From 5c2603f406cf4aeeadbd021dce3e97a447571940 Mon Sep 17 00:00:00 2001 From: jean-christophe81 <98889244+jean-christophe81@users.noreply.github.com> Date: Wed, 15 May 2024 10:36:58 +0200 Subject: [PATCH] enh(engine): Allow bypass the notification inhibitions for recovery notifications (#1260) REFS: MON-74111 --- engine/doc/engine-doc.md | 27 ++++++++++++ .../engine/configuration/extended_conf.hh | 5 ++- .../centreon/engine/configuration/state.hh | 3 ++ engine/src/configuration/applier/state.cc | 2 + engine/src/configuration/extended_conf.cc | 2 + engine/src/configuration/state.cc | 42 ++++++++++++++++++- engine/src/main.cc | 4 +- engine/src/notifier.cc | 27 ++++++++---- tests/engine/extended_conf.robot | 12 +----- tests/resources/Engine.py | 32 ++++++++++++++ tests/resources/resources.robot | 21 ++++++++++ 11 files changed, 154 insertions(+), 23 deletions(-) diff --git a/engine/doc/engine-doc.md b/engine/doc/engine-doc.md index d3aa94b0ede..aa1fa21912f 100644 --- a/engine/doc/engine-doc.md +++ b/engine/doc/engine-doc.md @@ -30,3 +30,30 @@ The main main job is done by whitelist class, it parses file and compares final This class is a singleton witch is replace by another instance each time conf is reloaded. Checkable class inherited by service and host classes keeps the last result of whitelist's check in cache in order to reduce CPU whitelist usage. + +## Extended configuration +Users can pass an additional configuration file to engine. Gorgone is not aware of this file, so users can override centengine.cfg configuration. +Each entry found in additional json configuration file overrides its twin in `centengine.cfg`. + +### examples of command line +```sh +/usr/sbin/centengine --config-file=/tmp/centengine_extend.json /etc/centreon-engine/centengine.cfg + +/usr/sbin/centengine --c /tmp/file1.json --c /tmp/file2.json /etc/centreon-engine/centengine.cfg +``` + +In the second case, values of file1.json will override values of centengine.cfg and values of file2.json will override values of file1.json + +### file format +```json +{ + "send_recovery_notifications_anyways": true +} +``` + +### implementation detail +In `state.cc` all setters have two methods: +* `apply_from_cfg` +* `apply_from_json`. + +On configuration update, we first parse the `centengine.cfg` and all the `*.cfg` files, and then we parse additional configuration files. diff --git a/engine/inc/com/centreon/engine/configuration/extended_conf.hh b/engine/inc/com/centreon/engine/configuration/extended_conf.hh index 48baeff71f0..fbd65a3c6c3 100644 --- a/engine/inc/com/centreon/engine/configuration/extended_conf.hh +++ b/engine/inc/com/centreon/engine/configuration/extended_conf.hh @@ -62,7 +62,10 @@ template void extended_conf::load_all(file_path_iterator begin, file_path_iterator end) { _confs.clear(); for (; begin != end; ++begin) { - _confs.emplace_back(std::make_unique(*begin)); + try { + _confs.emplace_back(std::make_unique(*begin)); + } catch (const std::exception&) { + } } } diff --git a/engine/inc/com/centreon/engine/configuration/state.hh b/engine/inc/com/centreon/engine/configuration/state.hh index 53f8b28997e..09b37a4512d 100644 --- a/engine/inc/com/centreon/engine/configuration/state.hh +++ b/engine/inc/com/centreon/engine/configuration/state.hh @@ -447,6 +447,8 @@ class state { void use_timezone(std::string const& value); bool use_true_regexp_matching() const noexcept; void use_true_regexp_matching(bool value); + bool use_send_recovery_notifications_anyways() const; + void use_send_recovery_notifications_anyways(bool value); using setter_map = absl::flat_hash_map>; @@ -650,6 +652,7 @@ class state { std::string _log_level_runtime; std::string _use_timezone; bool _use_true_regexp_matching; + bool _send_recovery_notifications_anyways; }; } // namespace com::centreon::engine::configuration diff --git a/engine/src/configuration/applier/state.cc b/engine/src/configuration/applier/state.cc index d65e5c5aa9c..ff8ae42e7cd 100644 --- a/engine/src/configuration/applier/state.cc +++ b/engine/src/configuration/applier/state.cc @@ -459,6 +459,8 @@ 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->use_true_regexp_matching(new_cfg.use_true_regexp_matching()); + config->use_send_recovery_notifications_anyways( + new_cfg.use_send_recovery_notifications_anyways()); config->user(new_cfg.user()); // Set this variable just the first time. diff --git a/engine/src/configuration/extended_conf.cc b/engine/src/configuration/extended_conf.cc index 24738e1c11e..bb0cbd846eb 100644 --- a/engine/src/configuration/extended_conf.cc +++ b/engine/src/configuration/extended_conf.cc @@ -33,6 +33,7 @@ std::list> extended_conf::_confs; extended_conf::extended_conf(const std::string& path) : _path(path) { if (::stat(_path.c_str(), &_file_info)) { SPDLOG_LOGGER_ERROR(log_v2::config(), "can't access to {}", _path); + throw exceptions::msg_fmt("can't access to {}", _path); } try { _content = common::rapidjson_helper::read_from_file(_path); @@ -42,6 +43,7 @@ extended_conf::extended_conf(const std::string& path) : _path(path) { log_v2::config(), "extended_conf::extended_conf : fail to read json content from {}: {}", _path, e.what()); + throw; } } diff --git a/engine/src/configuration/state.cc b/engine/src/configuration/state.cc index 12fbdcdae75..dd549b763cf 100644 --- a/engine/src/configuration/state.cc +++ b/engine/src/configuration/state.cc @@ -307,6 +307,8 @@ void state::_init_setter() { SETTER(bool, use_true_regexp_matching, "use_true_regexp_matching"); SETTER(std::string const&, _set_comment_file, "xcddefault_comment_file"); SETTER(std::string const&, _set_downtime_file, "xdddefault_downtime_file"); + SETTER(bool, use_send_recovery_notifications_anyways, + "send_recovery_notifications_anyways"); } // Default values. @@ -581,7 +583,8 @@ state::state() _log_level_process(default_log_level_process), _log_level_runtime(default_log_level_runtime), _use_timezone(default_use_timezone), - _use_true_regexp_matching(default_use_true_regexp_matching) { + _use_true_regexp_matching(default_use_true_regexp_matching), + _send_recovery_notifications_anyways(false) { static absl::once_flag _init_call_once; absl::call_once(_init_call_once, _init_setter); } @@ -764,6 +767,8 @@ state& state::operator=(state const& right) { _log_level_runtime = right._log_level_runtime; _use_timezone = right._use_timezone; _use_true_regexp_matching = right._use_true_regexp_matching; + _send_recovery_notifications_anyways = + right._send_recovery_notifications_anyways; } return *this; } @@ -928,7 +933,9 @@ bool state::operator==(state const& right) const noexcept { _log_level_process == right._log_level_process && _log_level_runtime == right._log_level_runtime && _use_timezone == right._use_timezone && - _use_true_regexp_matching == right._use_true_regexp_matching); + _use_true_regexp_matching == right._use_true_regexp_matching && + _send_recovery_notifications_anyways == + right._send_recovery_notifications_anyways); } /** @@ -4815,6 +4822,37 @@ void state::enable_macros_filter(bool value) { _enable_macros_filter = value; } +/** + * @brief Get _send_recovery_notifications_anyways + * + * Having a resource that has entered a non-OK state during a notification + * period and goes back to an OK state out of a notification period, then only + * if send_recovery_notifications_anyways is set to 1, the recovery notification + * must be sent to all users that have previously received the alert + * notification. + * + * @return true + * @return false + */ +bool state::use_send_recovery_notifications_anyways() const { + return _send_recovery_notifications_anyways; +} + +/** + * @brief + * + * Having a resource that has entered a non-OK state during a notification + * period and goes back to an OK state out of a notification period, then only + * if send_recovery_notifications_anyways is set to 1, the recovery notification + * must be sent to all users that have previously received the alert + * notification. + * + * @param value true if have to nitify anyway + */ +void state::use_send_recovery_notifications_anyways(bool value) { + _send_recovery_notifications_anyways = value; +} + /** * @brief modify state according json passed in parameter * diff --git a/engine/src/main.cc b/engine/src/main.cc index 8953c0ccc67..2581b42c043 100644 --- a/engine/src/main.cc +++ b/engine/src/main.cc @@ -127,7 +127,7 @@ int main(int argc, char* argv[]) { bool display_license(false); bool error(false); bool diagnose(false); - std::set extended_conf_file; + std::vector extended_conf_file; // Process all command line arguments. int c; @@ -161,7 +161,7 @@ int main(int argc, char* argv[]) { break; case 'c': if (optarg) - extended_conf_file.insert(optarg); + extended_conf_file.emplace_back(optarg); break; default: error = true; diff --git a/engine/src/notifier.cc b/engine/src/notifier.cc index 2f99e7e06fa..e9303502d45 100644 --- a/engine/src/notifier.cc +++ b/engine/src/notifier.cc @@ -458,16 +458,27 @@ bool notifier::_is_notification_viable_recovery(reason_type type std::time_t now; std::time(&now); + // 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)) { - engine_logger(dbg_notifications, more) - << "This notifier shouldn't have notifications sent out " - "at this time."; - SPDLOG_LOGGER_DEBUG(log_v2::notifications(), - "This notifier shouldn't have notifications sent out " - "at this time."); - retval = false; - send_later = true; + if (config->use_send_recovery_notifications_anyways()) { + SPDLOG_LOGGER_DEBUG(log_v2::notifications(), + "send_recovery_notifications_anyways flag enabled, " + "recovery notification is viable even if we are " + "out of timeperiod at this time."); + } else { + engine_logger(dbg_notifications, more) + << "This notifier shouldn't have notifications sent out " + "at this time."; + SPDLOG_LOGGER_DEBUG( + log_v2::notifications(), + "This notifier shouldn't have notifications sent out " + "at this time."); + retval = false; + send_later = true; + } } + /* if this notifier is currently in a scheduled downtime period, don't send * the notification */ else if (is_in_downtime()) { diff --git a/tests/engine/extended_conf.robot b/tests/engine/extended_conf.robot index 12e45841a09..747faa13a6f 100644 --- a/tests/engine/extended_conf.robot +++ b/tests/engine/extended_conf.robot @@ -20,11 +20,7 @@ EXT_CONF1 Ctn Config Broker module ${1} Create File /tmp/centengine_extend.json {"log_level_checks": "trace", "log_level_comments": "debug"} ${start} Get Current Date - Start Process - ... /usr/sbin/centengine - ... --config-file\=/tmp/centengine_extend.json - ... ${EtcRoot}/centreon-engine/config0/centengine.cfg - ... alias=e0 + Ctn Start Engine With Extend Conf Ctn Wait For Engine To Be Ready ${start} ${1} ${level} Ctn Get Engine Log Level 50001 checks Should Be Equal ${level} trace log_level_checks must be the extended conf value @@ -38,11 +34,7 @@ EXT_CONF2 Ctn Config Broker module ${1} Create File /tmp/centengine_extend.json {} ${start} Get Current Date - Start Process - ... /usr/sbin/centengine - ... --config-file\=/tmp/centengine_extend.json - ... ${EtcRoot}/centreon-engine/config0/centengine.cfg - ... alias=e0 + Ctn Start Engine With Extend Conf Ctn Wait For Engine To Be Ready ${start} ${1} Create File /tmp/centengine_extend.json {"log_level_checks": "trace", "log_level_comments": "debug"} diff --git a/tests/resources/Engine.py b/tests/resources/Engine.py index 7ec75ded7f7..8052af77d44 100755 --- a/tests/resources/Engine.py +++ b/tests/resources/Engine.py @@ -6,6 +6,8 @@ import engine_pb2 import engine_pb2_grpc from array import array +from dateutil import parser +import datetime from os import makedirs, chmod from os.path import exists, dirname from robot.api import logger @@ -3180,3 +3182,33 @@ def ctn_get_engine_log_level(port, log, timeout=TIMEOUT): except: logger.console("gRPC server not ready") + + + +def ctn_create_single_day_time_period(idx: int, time_period_name: str, date, minute_duration: int): + """ + Create a single day time period with a single time range from date to date + minute_duration + Args + idx: poller index + time_period_name: must be unique + date: time range start + minute_duration: time range length in minutes + """ + try: + my_date = parser.parse(date) + except: + my_date = datetime.fromtimestamp(date) + + filename = f"{ETC_ROOT}/centreon-engine/config{idx}/timeperiods.cfg" + + begin = my_date.time() + end = my_date + datetime.timedelta(minutes=minute_duration) + + with open(filename, "a+") as f: + f.write(f""" +define timeperiod {{ + timeperiod_name {time_period_name} + alias {time_period_name} + {my_date.date().isoformat()} {begin.strftime("%H:%M")}-{end.time().strftime("%H:%M")} +}} +""") diff --git a/tests/resources/resources.robot b/tests/resources/resources.robot index e94bdc4aaac..d8e0ab06d36 100644 --- a/tests/resources/resources.robot +++ b/tests/resources/resources.robot @@ -156,6 +156,27 @@ Ctn Start Engine Start Process /usr/sbin/centengine ${conf} alias=${alias} END +Ctn Start Engine With Extend Conf + ${count} Ctn Get Engines Count + FOR ${idx} IN RANGE 0 ${count} + ${alias} Catenate SEPARATOR= e ${idx} + ${conf} Catenate SEPARATOR= ${EtcRoot} /centreon-engine/config ${idx} /centengine.cfg + ${log} Catenate SEPARATOR= ${VarRoot} /log/centreon-engine/config ${idx} + ${lib} Catenate SEPARATOR= ${VarRoot} /lib/centreon-engine/config ${idx} + Create Directory ${log} + Create Directory ${lib} + TRY + Remove File ${lib}/rw/centengine.cmd + EXCEPT + Log can't remove ${lib}/rw/centengine.cmd don't worry + END + Start Process + ... /usr/sbin/centengine + ... --config-file\=/tmp/centengine_extend.json + ... ${conf} + ... alias=${alias} + END + Ctn Restart Engine Ctn Stop Engine Ctn Start Engine