From 6f03cfd240719721bd06a69036ee3c2b0efe7a1f Mon Sep 17 00:00:00 2001 From: Bernd Arnold Date: Thu, 30 Jun 2022 17:42:20 +0200 Subject: [PATCH 001/415] Docs: Add missing angle bracket --- doc/12-icinga2-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/12-icinga2-api.md b/doc/12-icinga2-api.md index 409e27b17..5b7eacca3 100644 --- a/doc/12-icinga2-api.md +++ b/doc/12-icinga2-api.md @@ -565,7 +565,7 @@ created by the API. ### Querying Objects You can request information about configuration objects by sending -a `GET` query to the `/v1/objects/` URL endpoint. `` URL endpoint. `` has to be replaced with the plural name of the object type you are interested in: From 5e924508773b71e9df3fe032b73f3a7359e7752d Mon Sep 17 00:00:00 2001 From: Yannick Martin Date: Mon, 11 Sep 2023 16:29:15 +0200 Subject: [PATCH 002/415] icinga2: address comment loading where host reference is not found address #9752: check if host reference is valid --- lib/icinga/comment.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/icinga/comment.cpp b/lib/icinga/comment.cpp index 9c0b92359..846735262 100644 --- a/lib/icinga/comment.cpp +++ b/lib/icinga/comment.cpp @@ -65,7 +65,7 @@ void Comment::OnAllConfigLoaded() Host::Ptr host = Host::GetByName(GetHostName()); - if (GetServiceName().IsEmpty()) + if (GetServiceName().IsEmpty() || ! host) m_Checkable = host; else m_Checkable = host->GetServiceByShortName(GetServiceName()); From d9e3a9c71b9be0da59e5f9209cad0db47bbcc8b9 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 11 Mar 2024 12:44:06 +0100 Subject: [PATCH 003/415] AUTHORS: add Yannick Martin --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index aa650e43e..88cee7252 100644 --- a/AUTHORS +++ b/AUTHORS @@ -293,6 +293,7 @@ Winfried Angele Wolfgang Nieder XnS Yannick Charton +Yannick Martin Yohan Jarosz Yonas Habteab Zachary McGibbon From a2194367080c779f46a3585d9571a788368e80c1 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Wed, 3 Apr 2024 14:58:33 +0200 Subject: [PATCH 004/415] check_systemd: Fix executable name by dropping .py The executable name for check_systemd's dropped the `.py` suffix for version 2.0.3[0], released in April 2019[1]. However, the old name is still being referenced, both in documentation as well as in the ITL's CheckCommand's command, making it unusable. Closes #9547. [0]: https://github.com/Josef-Friedrich/check_systemd/compare/v2.0.2...v2.0.3#diff-60f61ab7a8d1910d86d9fda2261620314edcae5894d5aaa236b821c7256badd7 [1]: https://github.com/Josef-Friedrich/check_systemd/releases/tag/v2.0.3 --- doc/03-monitoring-basics.md | 4 ++-- doc/05-service-monitoring.md | 8 ++++---- doc/10-icinga-template-library.md | 2 +- itl/plugins-contrib.d/systemd.conf | 2 +- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/doc/03-monitoring-basics.md b/doc/03-monitoring-basics.md index 06ea0c166..1124d1b4c 100644 --- a/doc/03-monitoring-basics.md +++ b/doc/03-monitoring-basics.md @@ -2135,7 +2135,7 @@ In order to find out about the command argument, call the plugin's help or consult the README. ``` -./check_systemd.py --help +./check_systemd --help ... @@ -2194,7 +2194,7 @@ With the [example above](03-monitoring-basics.md#command-arguments-value), inspect the parameter's help text. ``` -./check_systemd.py --help +./check_systemd --help ... diff --git a/doc/05-service-monitoring.md b/doc/05-service-monitoring.md index 097fb1184..9f188132b 100644 --- a/doc/05-service-monitoring.md +++ b/doc/05-service-monitoring.md @@ -281,10 +281,10 @@ that [it works](05-service-monitoring.md#service-monitoring-plugins-it-works). T `--help` parameter to see the actual parameters (docs might be outdated). ``` -./check_systemd.py --help +./check_systemd --help -usage: check_systemd.py [-h] [-c SECONDS] [-e UNIT | -u UNIT] [-v] [-V] - [-w SECONDS] +usage: check_systemd [-h] [-c SECONDS] [-e UNIT | -u UNIT] [-v] [-V] + [-w SECONDS] ... @@ -319,7 +319,7 @@ Start with the basic plugin call without any parameters. ``` object CheckCommand "systemd" { // Plugin name without 'check_' prefix - command = [ PluginContribDir + "/check_systemd.py" ] // Use the 'PluginContribDir' constant, see the contributed ITL commands + command = [ PluginContribDir + "/check_systemd" ] // Use the 'PluginContribDir' constant, see the contributed ITL commands } ``` diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md index 40cafba19..f1abee769 100644 --- a/doc/10-icinga-template-library.md +++ b/doc/10-icinga-template-library.md @@ -3652,7 +3652,7 @@ iostat\_cwrite | **Required.** Critical threshold for KB/s writes (default: 200) #### systemd -The [check_systemd.py](https://github.com/Josef-Friedrich/check_systemd) plugin +The [check_systemd](https://github.com/Josef-Friedrich/check_systemd) plugin will report a degraded system to your monitoring solution. It requires only the [nagiosplugin](https://nagiosplugin.readthedocs.io/en/stable) library. Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): diff --git a/itl/plugins-contrib.d/systemd.conf b/itl/plugins-contrib.d/systemd.conf index 4c0bbca17..236499bc4 100644 --- a/itl/plugins-contrib.d/systemd.conf +++ b/itl/plugins-contrib.d/systemd.conf @@ -1,7 +1,7 @@ /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ object CheckCommand "systemd" { - command = [ PluginContribDir + "/check_systemd.py" ] + command = [ PluginContribDir + "/check_systemd" ] arguments = { "--unit" = { From 90d08faa9ca19902ed0f2325734ebb99dfbe04fe Mon Sep 17 00:00:00 2001 From: Robert Scheck Date: Thu, 16 May 2019 12:44:29 +0200 Subject: [PATCH 005/415] Strip '\r' in notification messages to avoid 'Content-Type: application/octet-stream' Without this patch, an accidential `\r` in e.g. `$NOTIFICATIONCOMMENT` leads to a `Content-Type: application/octet-stream` header in e-mails. The accidential `\r` might slip in usually using Icinga/Nagios apps... --- etc/icinga2/scripts/mail-host-notification.sh | 8 +++++--- etc/icinga2/scripts/mail-service-notification.sh | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/etc/icinga2/scripts/mail-host-notification.sh b/etc/icinga2/scripts/mail-host-notification.sh index 70d3b5005..b1e8dd6ff 100755 --- a/etc/icinga2/scripts/mail-host-notification.sh +++ b/etc/icinga2/scripts/mail-host-notification.sh @@ -165,13 +165,15 @@ if [ -n "$MAILFROM" ] ; then ## Debian/Ubuntu use mailutils which requires `-a` to append the header if [ -f /etc/debian_version ]; then - /usr/bin/printf "%b" "$NOTIFICATION_MESSAGE" | $MAILBIN -a "From: $MAILFROM" -s "$SUBJECT" $USEREMAIL + /usr/bin/printf "%b" "$NOTIFICATION_MESSAGE" | tr -d '\015' \ + | $MAILBIN -a "From: $MAILFROM" -s "$SUBJECT" $USEREMAIL ## Other distributions (RHEL/SUSE/etc.) prefer mailx which sets a sender address with `-r` else - /usr/bin/printf "%b" "$NOTIFICATION_MESSAGE" | $MAILBIN -r "$MAILFROM" -s "$SUBJECT" $USEREMAIL + /usr/bin/printf "%b" "$NOTIFICATION_MESSAGE" | tr -d '\015' \ + | $MAILBIN -r "$MAILFROM" -s "$SUBJECT" $USEREMAIL fi else - /usr/bin/printf "%b" "$NOTIFICATION_MESSAGE" \ + /usr/bin/printf "%b" "$NOTIFICATION_MESSAGE" | tr -d '\015' \ | $MAILBIN -s "$SUBJECT" $USEREMAIL fi diff --git a/etc/icinga2/scripts/mail-service-notification.sh b/etc/icinga2/scripts/mail-service-notification.sh index 31d9137b8..164272f8f 100755 --- a/etc/icinga2/scripts/mail-service-notification.sh +++ b/etc/icinga2/scripts/mail-service-notification.sh @@ -178,13 +178,15 @@ if [ -n "$MAILFROM" ] ; then ## Debian/Ubuntu use mailutils which requires `-a` to append the header if [ -f /etc/debian_version ]; then - /usr/bin/printf "%b" "$NOTIFICATION_MESSAGE" | $MAILBIN -a "From: $MAILFROM" -s "$SUBJECT" $USEREMAIL + /usr/bin/printf "%b" "$NOTIFICATION_MESSAGE" | tr -d '\015' \ + | $MAILBIN -a "From: $MAILFROM" -s "$SUBJECT" $USEREMAIL ## Other distributions (RHEL/SUSE/etc.) prefer mailx which sets a sender address with `-r` else - /usr/bin/printf "%b" "$NOTIFICATION_MESSAGE" | $MAILBIN -r "$MAILFROM" -s "$SUBJECT" $USEREMAIL + /usr/bin/printf "%b" "$NOTIFICATION_MESSAGE" | tr -d '\015' \ + | $MAILBIN -r "$MAILFROM" -s "$SUBJECT" $USEREMAIL fi else - /usr/bin/printf "%b" "$NOTIFICATION_MESSAGE" \ + /usr/bin/printf "%b" "$NOTIFICATION_MESSAGE" | tr -d '\015' \ | $MAILBIN -s "$SUBJECT" $USEREMAIL fi From f9adf181111a54a7cccbfabc4514a7a66fbc53fb Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 15 May 2024 12:55:41 +0200 Subject: [PATCH 006/415] IcingaDB#SerializeState(): limit execution_time and latency to 2^32-1 not to write higher values into Redis than the Icinga DB schema can hold. This fixes yet another potential Go daemon crash. --- lib/icingadb/icingadb-objects.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index ff7a833d1..948751460 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -2642,8 +2642,8 @@ Dictionary::Ptr IcingaDB::SerializeState(const Checkable::Ptr& checkable) if (!cr->GetCommand().IsEmpty()) attrs->Set("check_commandline", FormatCommandLine(cr->GetCommand())); - attrs->Set("execution_time", TimestampToMilliseconds(fmax(0.0, cr->CalculateExecutionTime()))); - attrs->Set("latency", TimestampToMilliseconds(cr->CalculateLatency())); + attrs->Set("execution_time", std::min((long long)UINT32_MAX, TimestampToMilliseconds(fmax(0.0, cr->CalculateExecutionTime())))); + attrs->Set("latency", std::min((long long)UINT32_MAX, TimestampToMilliseconds(cr->CalculateLatency()))); attrs->Set("check_source", cr->GetCheckSource()); attrs->Set("scheduling_source", cr->GetSchedulingSource()); } From 2218ebd6b0a1c16fe8a578cbefe60745d0300b83 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 1 Mar 2024 10:11:17 +0100 Subject: [PATCH 007/415] `ConfigObjectUtility`: Use `AtomicFile` to store object config files --- lib/remote/configobjectutility.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/remote/configobjectutility.cpp b/lib/remote/configobjectutility.cpp index 62c910b41..9b5cf88b5 100644 --- a/lib/remote/configobjectutility.cpp +++ b/lib/remote/configobjectutility.cpp @@ -5,6 +5,7 @@ #include "remote/apilistener.hpp" #include "config/configcompiler.hpp" #include "config/configitem.hpp" +#include "base/atomic-file.hpp" #include "base/configwriter.hpp" #include "base/exception.hpp" #include "base/dependencygraph.hpp" @@ -198,11 +199,10 @@ bool ConfigObjectUtility::CreateObject(const Type::Ptr& type, const String& full return false; } + // AtomicFile doesn't create not yet existing directories, so we have to do it by ourselves. Utility::MkDirP(Utility::DirName(path), 0700); - std::ofstream fp(path.CStr(), std::ofstream::out | std::ostream::trunc); - fp << config; - fp.close(); + AtomicFile::Write(path, 0644, config); std::unique_ptr expr = ConfigCompiler::CompileFile(path, String(), "_api"); From 1a55b68541d540cdfa7e019a2784cbf01a30412a Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 8 Mar 2024 09:58:32 +0100 Subject: [PATCH 008/415] Introduce RAII style `ObjectNameLock` class --- lib/remote/configobjectslock.cpp | 43 +++++++++++++++++++++++++++++--- lib/remote/configobjectslock.hpp | 30 ++++++++++++++++++++++ 2 files changed, 70 insertions(+), 3 deletions(-) diff --git a/lib/remote/configobjectslock.cpp b/lib/remote/configobjectslock.cpp index e529c832b..f2165f2ce 100644 --- a/lib/remote/configobjectslock.cpp +++ b/lib/remote/configobjectslock.cpp @@ -1,13 +1,16 @@ /* Icinga 2 | (c) 2022 Icinga GmbH | GPLv2+ */ -#ifndef _WIN32 - -#include "base/shared-memory.hpp" #include "remote/configobjectslock.hpp" + +#ifndef _WIN32 +#include "base/shared-memory.hpp" #include +#endif /* _WIN32 */ using namespace icinga; +#ifndef _WIN32 + // On *nix one process may write config objects while another is loading the config, so this uses IPC. static SharedMemory l_ConfigObjectsMutex; @@ -22,3 +25,37 @@ ConfigObjectsSharedLock::ConfigObjectsSharedLock(std::try_to_lock_t) } #endif /* _WIN32 */ + +std::mutex ObjectNameLock::m_Mutex; +std::condition_variable ObjectNameLock::m_CV; +std::map> ObjectNameLock::m_LockedObjectNames; + +/** + * Locks the specified object name of the given type and unlocks it upon destruction of the instance of this class. + * + * If it is already locked, the call blocks until the lock is released. + * + * @param Type::Ptr ptype The type of the object you want to lock + * @param String objName The object name you want to lock + */ +ObjectNameLock::ObjectNameLock(const Type::Ptr& ptype, const String& objName): m_ObjectName{objName}, m_Type{ptype} +{ + std::unique_lock lock(m_Mutex); + m_CV.wait(lock, [this]{ + auto& locked = m_LockedObjectNames[m_Type.get()]; + return locked.find(m_ObjectName) == locked.end(); + }); + + // Add the object name to the locked list to block all other threads that try + // to process a message affecting the same object. + m_LockedObjectNames[ptype.get()].emplace(objName); +} + +ObjectNameLock::~ObjectNameLock() +{ + { + std::unique_lock lock(m_Mutex); + m_LockedObjectNames[m_Type.get()].erase(m_ObjectName); + } + m_CV.notify_all(); +} diff --git a/lib/remote/configobjectslock.hpp b/lib/remote/configobjectslock.hpp index ee909815f..6b75139b6 100644 --- a/lib/remote/configobjectslock.hpp +++ b/lib/remote/configobjectslock.hpp @@ -2,7 +2,12 @@ #pragma once +#include "base/type.hpp" +#include "base/string.hpp" +#include +#include #include +#include #ifndef _WIN32 #include @@ -69,4 +74,29 @@ private: #endif /* _WIN32 */ + +/** + * Allows you to easily lock/unlock a specific object of a given type by its name. + * + * That way, locking an object "this" of type Host does not affect an object "this" of + * type "Service" nor an object "other" of type "Host". + * + * @ingroup remote + */ +class ObjectNameLock +{ +public: + ObjectNameLock(const Type::Ptr& ptype, const String& objName); + + ~ObjectNameLock(); + +private: + String m_ObjectName; + Type::Ptr m_Type; + + static std::mutex m_Mutex; + static std::condition_variable m_CV; + static std::map> m_LockedObjectNames; +}; + } From 433e2de13ab86b6ed518865f447fa1f230e8fd73 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 8 Mar 2024 10:09:53 +0100 Subject: [PATCH 009/415] ApiListener: Process cluster config updates sequentially --- lib/remote/apilistener-configsync.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/remote/apilistener-configsync.cpp b/lib/remote/apilistener-configsync.cpp index a12db0bca..04436ad8b 100644 --- a/lib/remote/apilistener-configsync.cpp +++ b/lib/remote/apilistener-configsync.cpp @@ -8,6 +8,7 @@ #include "base/json.hpp" #include "base/convert.hpp" #include "config/vmops.hpp" +#include "remote/configobjectslock.hpp" #include using namespace icinga; @@ -104,6 +105,11 @@ Value ApiListener::ConfigUpdateObjectAPIHandler(const MessageOrigin::Ptr& origin return Empty; } + // Wait for the object name to become available for processing and block it immediately. + // Doing so guarantees that only one (create/update/delete) cluster event or API request of a + // given object is being processed at any given time. + ObjectNameLock objectNameLock(ptype, objName); + ConfigObject::Ptr object = ctype->GetObject(objName); String config = params->Get("config"); @@ -258,6 +264,11 @@ Value ApiListener::ConfigDeleteObjectAPIHandler(const MessageOrigin::Ptr& origin return Empty; } + // Wait for the object name to become available for processing and block it immediately. + // Doing so guarantees that only one (create/update/delete) cluster event or API request of a + // given object is being processed at any given time. + ObjectNameLock objectNameLock(ptype, objName); + ConfigObject::Ptr object = ctype->GetObject(objName); if (!object) { From 099f664ce65c7b825b9cc6e79178725d34b10a21 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 8 Mar 2024 10:16:33 +0100 Subject: [PATCH 010/415] `ConfigObjectUtility#CreateObject()`: Use `Defer` for config path cleanup --- lib/remote/configobjectutility.cpp | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/remote/configobjectutility.cpp b/lib/remote/configobjectutility.cpp index 9b5cf88b5..60268f6e1 100644 --- a/lib/remote/configobjectutility.cpp +++ b/lib/remote/configobjectutility.cpp @@ -7,6 +7,7 @@ #include "config/configitem.hpp" #include "base/atomic-file.hpp" #include "base/configwriter.hpp" +#include "base/defer.hpp" #include "base/exception.hpp" #include "base/dependencygraph.hpp" #include "base/tlsutility.hpp" @@ -204,6 +205,12 @@ bool ConfigObjectUtility::CreateObject(const Type::Ptr& type, const String& full AtomicFile::Write(path, 0644, config); + // Remove the just created config file in all the error cases and if the object creation + // succeeds the deferred callback will be cancelled. + Defer removeConfigPath([&path]{ + Utility::Remove(path); + }); + std::unique_ptr expr = ConfigCompiler::CompileFile(path, String(), "_api"); try { @@ -227,8 +234,6 @@ bool ConfigObjectUtility::CreateObject(const Type::Ptr& type, const String& full Log(LogNotice, "ConfigObjectUtility") << "Failed to commit config item '" << fullName << "'. Aborting and removing config path '" << path << "'."; - Utility::Remove(path); - for (const boost::exception_ptr& ex : upq.GetExceptions()) { errors->Add(DiagnosticInformation(ex, false)); @@ -250,8 +255,6 @@ bool ConfigObjectUtility::CreateObject(const Type::Ptr& type, const String& full Log(LogNotice, "ConfigObjectUtility") << "Failed to activate config object '" << fullName << "'. Aborting and removing config path '" << path << "'."; - Utility::Remove(path); - for (const boost::exception_ptr& ex : upq.GetExceptions()) { errors->Add(DiagnosticInformation(ex, false)); @@ -275,16 +278,16 @@ bool ConfigObjectUtility::CreateObject(const Type::Ptr& type, const String& full ConfigObject::Ptr obj = ctype->GetObject(fullName); if (obj) { + // Object is successfully created and activated, so don't remove its config. + removeConfigPath.Cancel(); + Log(LogInformation, "ConfigObjectUtility") << "Created and activated object '" << fullName << "' of type '" << type->GetName() << "'."; } else { Log(LogNotice, "ConfigObjectUtility") << "Object '" << fullName << "' was not created but ignored due to errors."; } - } catch (const std::exception& ex) { - Utility::Remove(path); - if (errors) errors->Add(DiagnosticInformation(ex, false)); From 546dea95a2b4fea316cd47a9ccfeb3dcff87155e Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 11 Mar 2024 12:34:14 +0100 Subject: [PATCH 011/415] Don't allow to modify/create/delete an object concurrently --- lib/remote/createobjecthandler.cpp | 3 +++ lib/remote/deleteobjecthandler.cpp | 3 +++ lib/remote/modifyobjecthandler.cpp | 3 +++ 3 files changed, 9 insertions(+) diff --git a/lib/remote/createobjecthandler.cpp b/lib/remote/createobjecthandler.cpp index 598eeec3b..89977a3d3 100644 --- a/lib/remote/createobjecthandler.cpp +++ b/lib/remote/createobjecthandler.cpp @@ -124,6 +124,9 @@ bool CreateObjectHandler::HandleRequest( return true; } + // Lock the object name of the given type to prevent from being created concurrently. + ObjectNameLock objectNameLock(type, name); + if (!ConfigObjectUtility::CreateObject(type, name, config, errors, diagnosticInformation)) { result1->Set("errors", errors); result1->Set("code", 500); diff --git a/lib/remote/deleteobjecthandler.cpp b/lib/remote/deleteobjecthandler.cpp index d79d7ef1f..6a7d194d4 100644 --- a/lib/remote/deleteobjecthandler.cpp +++ b/lib/remote/deleteobjecthandler.cpp @@ -84,6 +84,9 @@ bool DeleteObjectHandler::HandleRequest( Array::Ptr errors = new Array(); Array::Ptr diagnosticInformation = new Array(); + // Lock the object name of the given type to prevent from being modified/deleted concurrently. + ObjectNameLock objectNameLock(type, obj->GetName()); + if (!ConfigObjectUtility::DeleteObject(obj, cascade, errors, diagnosticInformation)) { code = 500; status = "Object could not be deleted."; diff --git a/lib/remote/modifyobjecthandler.cpp b/lib/remote/modifyobjecthandler.cpp index d6fa98b2e..a817faad8 100644 --- a/lib/remote/modifyobjecthandler.cpp +++ b/lib/remote/modifyobjecthandler.cpp @@ -112,6 +112,9 @@ bool ModifyObjectHandler::HandleRequest( String key; + // Lock the object name of the given type to prevent from being modified/deleted concurrently. + ObjectNameLock objectNameLock(type, obj->GetName()); + try { if (restoreAttrs) { ObjectLock oLock (restoreAttrs); From ba52e2ed519505bbba3c0522aa82129355b2377c Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 1 Jul 2024 12:44:43 +0200 Subject: [PATCH 012/415] GHA: drop EOL EL7 --- .github/workflows/linux.bash | 12 +----------- .github/workflows/linux.yml | 1 - 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/.github/workflows/linux.bash b/.github/workflows/linux.bash index fe0e7d551..51b978f5c 100755 --- a/.github/workflows/linux.bash +++ b/.github/workflows/linux.bash @@ -1,7 +1,7 @@ #!/bin/bash set -exo pipefail -export PATH="/usr/lib/ccache:/usr/lib64/ccache:/opt/rh/devtoolset-11/root/usr/bin:$PATH" +export PATH="/usr/lib/ccache:/usr/lib64/ccache:$PATH" export CCACHE_DIR=/icinga2/ccache export CTEST_OUTPUT_ON_FAILURE=1 CMAKE_OPTS='' @@ -33,16 +33,6 @@ case "$DISTRO" in {boost,libedit,mariadb1\*,ncurses,openssl,postgresql,systemd}-devel ;; - centos:*) - yum install -y centos-release-scl epel-release - yum install -y bison ccache cmake3 devtoolset-11-gcc-c++ flex ninja-build \ - {boost169,libedit,mariadb,ncurses,openssl,postgresql,systemd}-devel - - ln -vs /usr/bin/cmake3 /usr/local/bin/cmake - ln -vs /usr/bin/ccache /usr/lib64/ccache/g++ - CMAKE_OPTS='-DBOOST_INCLUDEDIR=/usr/include/boost169 -DBOOST_LIBRARYDIR=/usr/lib64/boost169' - ;; - debian:*|ubuntu:*) apt-get update DEBIAN_FRONTEND=noninteractive apt-get install --no-install-{recommends,suggests} -y bison \ diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 3eda27df2..b7d6269d4 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -23,7 +23,6 @@ jobs: distro: - amazonlinux:2 - amazonlinux:2023 - - centos:7 # and RHEL 7 - debian:11 # and Raspbian 11 - debian:12 # and Raspbian 12 - fedora:37 From 6f28fea5bb839551f25a10465206e83dff0735d8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Mon, 1 Jul 2024 13:29:01 +0200 Subject: [PATCH 013/415] GHA: add openSUSE 15.6 and SLES 15.6 --- .github/workflows/linux.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 3eda27df2..956b8eb56 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -33,6 +33,7 @@ jobs: - opensuse/leap:15.3 # SLES 15.3 - opensuse/leap:15.4 # and SLES 15.4 - opensuse/leap:15.5 # and SLES 15.5 + - opensuse/leap:15.6 # and SLES 15.6 - rockylinux:8 # RHEL 8 - rockylinux:9 # RHEL 9 - ubuntu:20.04 From 8db33e5b3cf4297860fa53e27d1a2135052a9d46 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Wed, 31 Jul 2024 10:23:05 +0200 Subject: [PATCH 014/415] GHA: Unbreak Windows Tests As seen in the recent GHA run for #10102, the two Windows Actions have failed. The output log contains: > DEBUG: 27+ >>>> ctest.exe -C "${env:CMAKE_BUILD_TYPE}" -T test -O $env:ICINGA2_BUILDPATH/Test.xml > --output-on-failure --log_level=all > CMake Error: Unknown argument: --log_level=all > CMake Error: Run 'ctest --help' for all supported options. After consulting ctest(1), older versions included, I have never found a mention of the "--log_level" flag. Since the useful "--output-on-failure" flag is already set, which will "[o]utput anything outputted by the test program if the test should fail", I do not see any further reason for more logging information. This flag was introduced in 7665143afa500dd589546665124293b9c1206265, but I have not found any reasoning for the flag in particular. --- tools/win32/test.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tools/win32/test.ps1 b/tools/win32/test.ps1 index d7ad90ce2..3d3c12540 100644 --- a/tools/win32/test.ps1 +++ b/tools/win32/test.ps1 @@ -24,7 +24,7 @@ if (-not ($env:PATH -contains $env:CMAKE_PATH)) { cd "$env:ICINGA2_BUILDPATH" -ctest.exe -C "${env:CMAKE_BUILD_TYPE}" -T test -O $env:ICINGA2_BUILDPATH/Test.xml --output-on-failure --log_level=all +ctest.exe -C "${env:CMAKE_BUILD_TYPE}" -T test -O $env:ICINGA2_BUILDPATH/Test.xml --output-on-failure if ($lastexitcode -ne 0) { cd .. exit $lastexitcode From 4daa03dc0210c8faa4ff2176bfbe5d1692a08be3 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 29 Jan 2024 08:28:31 +0100 Subject: [PATCH 015/415] Fix broken timeperiods/scheduleddowntimes --- lib/icinga/legacytimeperiod.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/icinga/legacytimeperiod.cpp b/lib/icinga/legacytimeperiod.cpp index 33e666544..1613c1687 100644 --- a/lib/icinga/legacytimeperiod.cpp +++ b/lib/icinga/legacytimeperiod.cpp @@ -31,7 +31,7 @@ bool LegacyTimePeriod::IsInTimeRange(const tm *begin, const tm *end, int stride, tsend = mktime_const(end); tsref = mktime_const(reference); - if (tsref < tsbegin || tsref > tsend) + if (tsref < tsbegin || tsref >= tsend) return false; int daynumber = (tsref - tsbegin) / (24 * 60 * 60); From 86347013a612e4116051089d08523186fb2bc83a Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 1 Aug 2024 16:13:10 +0200 Subject: [PATCH 016/415] Check segemnt start date inclusively in `TimePeriod::IsInside()` --- lib/icinga/timeperiod.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/icinga/timeperiod.cpp b/lib/icinga/timeperiod.cpp index db3272e61..e305b80ad 100644 --- a/lib/icinga/timeperiod.cpp +++ b/lib/icinga/timeperiod.cpp @@ -291,7 +291,7 @@ bool TimePeriod::IsInside(double ts) const if (segments) { ObjectLock dlock(segments); for (const Dictionary::Ptr& segment : segments) { - if (ts > segment->Get("begin") && ts < segment->Get("end")) + if (ts >= segment->Get("begin") && ts < segment->Get("end")) return true; } } From ddf7143777bd381cdbd788c2a2c5f8f45d554df6 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 29 Jan 2024 08:31:48 +0100 Subject: [PATCH 017/415] tests: Add some basic tests cases for `LegacyTimePeriod::IsInTimeRange()` --- test/CMakeLists.txt | 1 + test/icinga-legacytimeperiod.cpp | 44 ++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d2ce3a0ce..9baf56ddb 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -185,6 +185,7 @@ add_boost_test(base icinga_notification/recovery_filter_duplicate icinga_macros/simple icinga_legacytimeperiod/simple + icinga_legacytimeperiod/is_in_range icinga_legacytimeperiod/advanced icinga_legacytimeperiod/dst icinga_legacytimeperiod/dst_isinside diff --git a/test/icinga-legacytimeperiod.cpp b/test/icinga-legacytimeperiod.cpp index e1150be57..132ffd233 100644 --- a/test/icinga-legacytimeperiod.cpp +++ b/test/icinga-legacytimeperiod.cpp @@ -213,6 +213,50 @@ BOOST_AUTO_TEST_CASE(simple) BOOST_CHECK_EQUAL(end, expectedEnd); } +BOOST_AUTO_TEST_CASE(is_in_range) +{ + tm tm_beg = Utility::LocalTime(1706518800); // 2024-01-29 09:00:00 UTC + tm tm_end = Utility::LocalTime(1706520600); // 2024-01-29 09:30:00 UTC + + tm reference = tm_beg; // 2024-01-29 09:00:00 UTC + + // The start date of the range should ofcourse be inside. + BOOST_CHECK_EQUAL(true, LegacyTimePeriod::IsInTimeRange(&tm_beg, &tm_end, 1, &reference)); + + reference = Utility::LocalTime(1706519400); // 2024-01-29 09:10:00 UTC + // The reference time is only 10 minutes behind the start date, which should be covered by this range. + BOOST_CHECK_EQUAL(true, LegacyTimePeriod::IsInTimeRange(&tm_beg, &tm_end, 1, &reference)); + + reference = Utility::LocalTime(1706518799); // 2024-01-29 08:59:59 UTC + + // The reference time is 1 second ahead of the range start date, which shouldn't be covered by this range. + BOOST_CHECK_EQUAL(false, LegacyTimePeriod::IsInTimeRange(&tm_beg, &tm_end, 1, &reference)); + + reference = Utility::LocalTime(1706520599); // 2024-01-29 09:29:59 UTC + + // The reference time is 1 second before the specified end time, so this should be in the range. + BOOST_CHECK_EQUAL(true, LegacyTimePeriod::IsInTimeRange(&tm_beg, &tm_end, 1, &reference)); + + reference = tm_end; // 2024-01-29 09:30:00 UTC + + // The reference time is exactly the same as the specified end time, so this should definitely not be in the range. + BOOST_CHECK_EQUAL(false, LegacyTimePeriod::IsInTimeRange(&tm_beg, &tm_end, 1, &reference)); + + tm_beg = Utility::LocalTime(1706518800); // 2024-01-29 09:00:00 UTC + tm_end = Utility::LocalTime(1706720400); // 2024-01-31 17:00:00 UTC + + reference = Utility::LocalTime(1706612400); // 2024-01-30 12:00:00 UTC + + // Even if the reference time is within the specified range, the stride guarantees that the reference + // should be 2 days after the range start date, which is not the case. + BOOST_CHECK_EQUAL(false, LegacyTimePeriod::IsInTimeRange(&tm_beg, &tm_end, 2, &reference)); + + reference = Utility::LocalTime(1706698800); // 2024-01-31 11:00:00 UTC + + // The reference time is now within the specified range and 2 days after the range start date. + BOOST_CHECK_EQUAL(true, LegacyTimePeriod::IsInTimeRange(&tm_beg, &tm_end, 2, &reference)); +} + struct DateTime { struct { From 4f94891b521acdc00ff19458c915cebc9753b38b Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 12 Jun 2024 18:17:44 +0200 Subject: [PATCH 018/415] Add advanced timeperiod range,include/exclude test cases --- test/CMakeLists.txt | 2 + test/icinga-legacytimeperiod.cpp | 148 +++++++++++++++++++++++++++++++ 2 files changed, 150 insertions(+) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9baf56ddb..331acafa5 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -186,6 +186,8 @@ add_boost_test(base icinga_macros/simple icinga_legacytimeperiod/simple icinga_legacytimeperiod/is_in_range + icinga_legacytimeperiod/out_of_range_segments + icinga_legacytimeperiod/include_exclude_timeperiods icinga_legacytimeperiod/advanced icinga_legacytimeperiod/dst icinga_legacytimeperiod/dst_isinside diff --git a/test/icinga-legacytimeperiod.cpp b/test/icinga-legacytimeperiod.cpp index 132ffd233..781913e4d 100644 --- a/test/icinga-legacytimeperiod.cpp +++ b/test/icinga-legacytimeperiod.cpp @@ -257,6 +257,154 @@ BOOST_AUTO_TEST_CASE(is_in_range) BOOST_CHECK_EQUAL(true, LegacyTimePeriod::IsInTimeRange(&tm_beg, &tm_end, 2, &reference)); } +BOOST_AUTO_TEST_CASE(out_of_range_segments) +{ + TimePeriod::Ptr tp = new TimePeriod(); + tp->SetUpdate(new Function("LegacyTimePeriod", LegacyTimePeriod::ScriptFunc, {"tp", "begin", "end"}), true); + + // A single day range shouldn't span to the following day too (see https://github.com/Icinga/icinga2/issues/9388). + tp->SetRanges(new Dictionary({{"2024-06-12", "00:00-24:00"}}), true); + tp->UpdateRegion(1718150400, 1718236800, true); // 2024-06-12 00:00:00 - 24:00:00 UTC + + BOOST_CHECK_EQUAL(true, tp->IsInside(1718200800)); // 2024-06-12 14:00:00 UTC + { + Array::Ptr segments = tp->GetSegments(); + BOOST_REQUIRE_EQUAL(1, segments->GetLength()); + + Dictionary::Ptr segment = segments->Get(0); + BOOST_CHECK_EQUAL(1718150400, segment->Get("begin")); // 2024-06-12 00:00:00 UTC + BOOST_CHECK_EQUAL(1718236800, segment->Get("end")); // 2024-06-12 24:00:00 UTC + } + tp->UpdateRegion(1718236800, 1718323200, true); // 2024-06-13 00:00:00 - 24:00:00 UTC + + BOOST_CHECK_EQUAL(false, tp->IsInside(1718287200)); // 2024-06-13 14:00:00 UTC + { + Array::Ptr segments = tp->GetSegments(); + BOOST_CHECK_EQUAL(0, segments->GetLength()); // There should be no segments at all! + } + + // One partially day range shouldn't contain more than a single segment (see https://github.com/Icinga/icinga2/issues/9781). + tp->SetRanges(new Dictionary({{"2024-06-12", "10:00-12:00"}}), true); + tp->UpdateRegion(1718150400, 1718236800, true); // 2024-06-12 00:00:00 - 24:00:00 UTC + + BOOST_CHECK_EQUAL(true, tp->IsInside(1718190000)); // 2024-06-12 11:00:00 UTC + { + Array::Ptr segments = tp->GetSegments(); + BOOST_REQUIRE_EQUAL(1, segments->GetLength()); + + Dictionary::Ptr segment = segments->Get(0); + BOOST_CHECK_EQUAL(1718186400, segment->Get("begin")); // 2024-06-12 10:00:00 UTC (range start date) + BOOST_CHECK_EQUAL(1718193600, segment->Get("end")); // 2024-06-12 12:00:00 UTC (range end date) + } + tp->UpdateRegion(1718236800, 1718323200, true); // 2024-06-13 00:00:00 - 24:00:00 UTC + + BOOST_CHECK_EQUAL(false, tp->IsInside(1718287200)); // 2024-06-13 14:00:00 UTC + BOOST_CHECK_EQUAL(0, tp->GetSegments()->GetLength()); // There should be no segments at all! +} + +BOOST_AUTO_TEST_CASE(include_exclude_timeperiods) +{ + Function::Ptr update = new Function("LegacyTimePeriod", LegacyTimePeriod::ScriptFunc, {"tp", "begin", "end"}); + TimePeriod::Ptr excludedTp = new TimePeriod(); + excludedTp->SetName("excluded", true); + excludedTp->SetUpdate(update, true); + excludedTp->SetRanges(new Dictionary({{"2024-06-11", "00:00-24:00"}}), true); + + excludedTp->UpdateRegion(1718064000, 1718323200, true); // 2024-06-11 00:00:00 - 2024-06-13 24:00:00 UTC + + BOOST_CHECK_EQUAL(1, excludedTp->GetSegments()->GetLength()); + BOOST_CHECK_EQUAL(true, excludedTp->IsInside(1718114400)); // 2024-06-11 14:00:00 UTC + BOOST_CHECK_EQUAL(false, excludedTp->IsInside(1718200800)); // 2024-06-12 14:00:00 UTC + BOOST_CHECK_EQUAL(false, excludedTp->IsInside(1718287200)); // 2024-06-13 14:00:00 UTC + + // Register the excluded timeperiod to make it globally visible. + excludedTp->Register(); + + Dictionary::Ptr ranges = new Dictionary({ + {"2024-06-11", "09:00-17:00"}, + {"2024-06-12", "09:00-17:00"}, + {"2024-06-13", "09:00-17:00"} + }); + + TimePeriod::Ptr tp = new TimePeriod(); + tp->SetExcludes(new Array({"excluded"}), true); + tp->SetUpdate(update, true); + tp->SetRanges(ranges, true); + tp->UpdateRegion(1718064000, 1718323200, true); // 2024-06-11 00:00:00 - 2024-06-13 24:00:00 UTC + + BOOST_CHECK_EQUAL(false, tp->IsInside(1718114400)); // 2024-06-11 14:00:00 UTC + BOOST_CHECK_EQUAL(false, tp->IsInside(1718150400)); // 2024-06-12 00:00:00 UTC + BOOST_CHECK_EQUAL(true, tp->IsInside(1718200800)); // 2024-06-12 14:00:00 UTC + BOOST_CHECK_EQUAL(false, tp->IsInside(1718323200)); // 2024-06-13 00:00:00 UTC + BOOST_CHECK_EQUAL(true, tp->IsInside(1718287200)); // 2024-06-13 14:00:00 UTC + { + Array::Ptr segments = tp->GetSegments(); + // The updated region is 2024-06-11 - 13, so there should only be 2 segements, when the excludes works correctly. + BOOST_REQUIRE_EQUAL(2, segments->GetLength()); + + Dictionary::Ptr segment = segments->Get(0); + BOOST_CHECK_EQUAL(1718182800, segment->Get("begin")); // 2024-06-12 09:00:00 UTC + BOOST_CHECK_EQUAL(1718211600, segment->Get("end")); // 2024-06-12 17:00:00 UTC + + BOOST_CHECK_EQUAL(true, tp->IsInside(segment->Get("begin"))); + BOOST_CHECK_EQUAL(false, tp->IsInside(segment->Get("end"))); + + BOOST_CHECK_EQUAL(false, excludedTp->IsInside(segment->Get("begin"))); + BOOST_CHECK_EQUAL(false, excludedTp->IsInside(segment->Get("end"))); + + segment = segments->Get(1); + BOOST_CHECK_EQUAL(1718269200, segment->Get("begin")); // 2024-06-13 09:00:00 UTC + BOOST_CHECK_EQUAL(1718298000, segment->Get("end")); // 2024-06-13 17:00:00 UTC + + BOOST_CHECK_EQUAL(true, tp->IsInside(segment->Get("begin"))); + BOOST_CHECK_EQUAL(false, tp->IsInside(segment->Get("end"))); + + BOOST_CHECK_EQUAL(false, excludedTp->IsInside(segment->Get("begin"))); + BOOST_CHECK_EQUAL(false, excludedTp->IsInside(segment->Get("end"))); + } + + // Include timeperiod test cases ... + TimePeriod::Ptr includedTp = new TimePeriod(); + includedTp->SetName("included", true); + includedTp->SetUpdate(update, true); + includedTp->SetRanges(new Dictionary({{"2024-06-11", "08:00-17:00"}}), true); + + includedTp->UpdateRegion(1718064000, 1718323200, true); // 2024-06-11 00:00:00 - 2024-06-13 24:00:00 UTC + + BOOST_CHECK_EQUAL(1, includedTp->GetSegments()->GetLength()); + BOOST_CHECK_EQUAL(true, includedTp->IsInside(1718114400)); // 2024-06-11 14:00:00 UTC + BOOST_CHECK_EQUAL(false, includedTp->IsInside(1718200800)); // 2024-06-12 14:00:00 UTC + BOOST_CHECK_EQUAL(false, includedTp->IsInside(1718287200)); // 2024-06-13 14:00:00 UTC + + // Register the timeperiod to make it globally visible. + includedTp->Register(); + + tp->SetIncludes(new Array({"included"}), true); + tp->UpdateRegion(1718064000, 1718323200, true); // 2024-06-11 00:00:00 - 2024-06-13 24:00:00 UTC + { + Array::Ptr segments = tp->GetSegments(); + // The updated region is 2024-06-11 - 13, so there should be 3 segements, when the *prefer* includes works correctly. + BOOST_REQUIRE_EQUAL(3, segments->GetLength()); + + Dictionary::Ptr segment = segments->Get(0); + BOOST_CHECK_EQUAL(1718182800, segment->Get("begin")); // 2024-06-12 09:00:00 UTC + BOOST_CHECK_EQUAL(1718211600, segment->Get("end")); // 2024-06-12 17:00:00 UTC + + segment = segments->Get(1); + BOOST_CHECK_EQUAL(1718269200, segment->Get("begin")); // 2024-06-13 09:00:00 UTC + BOOST_CHECK_EQUAL(1718298000, segment->Get("end")); // 2024-06-13 17:00:00 UTC + + segment = segments->Get(2); + BOOST_CHECK_EQUAL(1718092800, segment->Get("begin")); // 2024-06-11 08:00:00 UTC + BOOST_CHECK_EQUAL(1718125200, segment->Get("end")); // 2024-06-11 17:00:00 UTC + + BOOST_CHECK_EQUAL(true, tp->IsInside(segment->Get("begin"))); + BOOST_CHECK_EQUAL(true, includedTp->IsInside(segment->Get("begin"))); + BOOST_CHECK_EQUAL(false, tp->IsInside(segment->Get("end"))); + BOOST_CHECK_EQUAL(false, includedTp->IsInside(segment->Get("end"))); + } +} + struct DateTime { struct { From c4edecc1fbe585981348a41932d18771c0dce884 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 6 Aug 2024 16:14:14 +0200 Subject: [PATCH 019/415] Unregister invalid config objects properly --- lib/config/configitem.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/config/configitem.cpp b/lib/config/configitem.cpp index f54b90c95..bf4da81a4 100644 --- a/lib/config/configitem.cpp +++ b/lib/config/configitem.cpp @@ -472,13 +472,16 @@ bool ConfigItem::CommitNewItems(const ActivationContext::Ptr& context, WorkQueue continue; std::atomic committed_items(0); - std::mutex newItemsMutex; { auto items (itemsByType.find(type.get())); if (items != itemsByType.end()) { - upq.ParallelFor(items->second, [&committed_items, &newItems, &newItemsMutex](const ItemPair& ip) { + for (const ItemPair& pair: items->second) { + newItems.emplace_back(pair.first); + } + + upq.ParallelFor(items->second, [&committed_items](const ItemPair& ip) { const ConfigItem::Ptr& item = ip.first; if (!item->Commit(ip.second)) { @@ -490,9 +493,6 @@ bool ConfigItem::CommitNewItems(const ActivationContext::Ptr& context, WorkQueue } committed_items++; - - std::unique_lock lock(newItemsMutex); - newItems.emplace_back(item); }); upq.Join(); From c45829b59ff2b564136900dce91c9b3a84a03534 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Fri, 2 Aug 2024 16:27:15 +0200 Subject: [PATCH 020/415] Timeperiods: fix off by one when calculating n-th last weekday of the month A day specification like "monday -1" refers to the last Monday of the month. However, there was an off by one if the first day of the next month is the same day of the week, i.e. a Monday in this example. LegacyTimePeriod::FindNthWeekday() picks a day to start the search for the day in question. When given a negative n to search for the n-th last day, it wrongly used the first day of the following month as the start and counted it as if it was within the current month. This resulted in a 1/7 chance that the result was one week too late. This is fixed by using the last day of the current month instead. --- lib/icinga/legacytimeperiod.cpp | 9 ++++-- test/CMakeLists.txt | 1 + test/icinga-legacytimeperiod.cpp | 52 ++++++++++++++++++++++++++++++++ 3 files changed, 59 insertions(+), 3 deletions(-) diff --git a/lib/icinga/legacytimeperiod.cpp b/lib/icinga/legacytimeperiod.cpp index 33e666544..212ff8439 100644 --- a/lib/icinga/legacytimeperiod.cpp +++ b/lib/icinga/legacytimeperiod.cpp @@ -62,18 +62,21 @@ void LegacyTimePeriod::FindNthWeekday(int wday, int n, tm *reference) if (n > 0) { dir = 1; + + /* Postitive days are relative to the first day of the month. */ + t.tm_mday = 1; } else { n *= -1; dir = -1; - /* Negative days are relative to the next month. */ + /* Negative days are relative to the last day of the month which is + * what mktime() normalizes the 0th day of the next month to. */ t.tm_mon++; + t.tm_mday = 0; } ASSERT(n > 0); - t.tm_mday = 1; - for (;;) { // Always operate on 00:00:00 with automatic DST detection, otherwise days could // be skipped or counted twice if +-24 hours is not on the next or previous day. diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d2ce3a0ce..defc5e889 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -188,6 +188,7 @@ add_boost_test(base icinga_legacytimeperiod/advanced icinga_legacytimeperiod/dst icinga_legacytimeperiod/dst_isinside + icinga_legacytimeperiod/find_nth_weekday icinga_perfdata/empty icinga_perfdata/simple icinga_perfdata/quotes diff --git a/test/icinga-legacytimeperiod.cpp b/test/icinga-legacytimeperiod.cpp index e1150be57..50cad3f28 100644 --- a/test/icinga-legacytimeperiod.cpp +++ b/test/icinga-legacytimeperiod.cpp @@ -691,4 +691,56 @@ BOOST_AUTO_TEST_CASE(dst_isinside) } } +BOOST_AUTO_TEST_CASE(find_nth_weekday) { + auto run = [](const std::string& refDay, int wday, int n, const std::string& expectedDay) { + tm expected = make_tm(expectedDay + " 00:00:00"); + + tm t = make_tm(refDay + " 00:00:00"); + LegacyTimePeriod::FindNthWeekday(wday, n, &t); + + BOOST_CHECK_MESSAGE(mktime(&expected) == mktime(&t), + "[ref=" << refDay << ", wday=" << wday << ", n=" << n << "] " + "expected: " << pretty_time(expected) << ", " + "got: " << pretty_time(t)); + }; + + /* March 2019 + * Mo Tu We Th Fr Sa Su + * 1 2 3 + * 4 5 6 7 8 9 10 + * 11 12 13 14 15 16 17 + * 18 19 20 21 22 23 24 + * 25 26 27 28 29 30 31 + */ + + // Use every day of the month as reference day, all must give the same result for that month. + for (int i = 1; i <= 31; ++i) { + std::stringstream refDayStream; + refDayStream << "2019-03-" << std::setw(2) << std::setfill('0') << i; + std::string refDay = refDayStream.str(); + + const int monday = 1; // 4 ocurrences in March 2019 + run(refDay, monday, 1, "2019-03-04"); + run(refDay, monday, 2, "2019-03-11"); + run(refDay, monday, 3, "2019-03-18"); + run(refDay, monday, 4, "2019-03-25"); + run(refDay, monday, -1, "2019-03-25"); + run(refDay, monday, -2, "2019-03-18"); + run(refDay, monday, -3, "2019-03-11"); + run(refDay, monday, -4, "2019-03-04"); + + const int friday = 5; // 5 ocurrences in March 2019 + run(refDay, friday, 1, "2019-03-01"); + run(refDay, friday, 2, "2019-03-08"); + run(refDay, friday, 3, "2019-03-15"); + run(refDay, friday, 4, "2019-03-22"); + run(refDay, friday, 5, "2019-03-29"); + run(refDay, friday, -1, "2019-03-29"); + run(refDay, friday, -2, "2019-03-22"); + run(refDay, friday, -3, "2019-03-15"); + run(refDay, friday, -4, "2019-03-08"); + run(refDay, friday, -5, "2019-03-01"); + } +} + BOOST_AUTO_TEST_SUITE_END() From f3c7ac11e92a1547a2acfafc8080e2549091f3c2 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 5 Aug 2024 13:24:35 +0200 Subject: [PATCH 021/415] /v1/debug/malloc_info: call malloc_info(3) if available The GNU libc function malloc_info(3) provides memory allocation and usage statistics of Icinga 2 itself. --- CMakeLists.txt | 1 + config.h.cmake | 1 + doc/12-icinga2-api.md | 67 +++++++++++++++++++++++ lib/remote/CMakeLists.txt | 1 + lib/remote/mallocinfohandler.cpp | 94 ++++++++++++++++++++++++++++++++ lib/remote/mallocinfohandler.hpp | 27 +++++++++ 6 files changed, 191 insertions(+) create mode 100644 lib/remote/mallocinfohandler.cpp create mode 100644 lib/remote/mallocinfohandler.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 52fa1d868..6e531a059 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -365,6 +365,7 @@ check_function_exists(vfork HAVE_VFORK) check_function_exists(backtrace_symbols HAVE_BACKTRACE_SYMBOLS) check_function_exists(pipe2 HAVE_PIPE2) check_function_exists(nice HAVE_NICE) +check_function_exists(malloc_info HAVE_MALLOC_INFO) check_library_exists(dl dladdr "dlfcn.h" HAVE_DLADDR) check_library_exists(execinfo backtrace_symbols "" HAVE_LIBEXECINFO) check_include_file_cxx(cxxabi.h HAVE_CXXABI_H) diff --git a/config.h.cmake b/config.h.cmake index 3ed2ae46d..02ab9d7a0 100644 --- a/config.h.cmake +++ b/config.h.cmake @@ -8,6 +8,7 @@ #cmakedefine HAVE_LIBEXECINFO #cmakedefine HAVE_CXXABI_H #cmakedefine HAVE_NICE +#cmakedefine HAVE_MALLOC_INFO #cmakedefine HAVE_EDITLINE #cmakedefine HAVE_SYSTEMD diff --git a/doc/12-icinga2-api.md b/doc/12-icinga2-api.md index 82045365a..5a14c8e2d 100644 --- a/doc/12-icinga2-api.md +++ b/doc/12-icinga2-api.md @@ -288,6 +288,7 @@ Available permissions for specific URL endpoints: config/query | /v1/config | No | 1 config/modify | /v1/config | No | 512 console | /v1/console | No | 1 + debug | /v1/debug | No | 1 events/<type> | /v1/events | No | 1 objects/query/<type> | /v1/objects | Yes | 1 objects/create/<type> | /v1/objects | No | 1 @@ -2502,6 +2503,72 @@ curl -k -s -S -i -u root:icinga -H 'Accept: application/json' \ } ``` +## Memory Usage Analysis + +The GNU libc function `malloc_info(3)` provides memory allocation and usage +statistics of Icinga 2 itself. You can call it directly by sending a `GET` +request to the URL endpoint `/v1/debug/malloc_info`. + +The [API permission](12-icinga2-api.md#icinga2-api-permissions) `debug` is required. + +Example: + +```bash +curl -k -s -S -i -u root:icinga https://localhost:5665/v1/debug/malloc_info +``` + +In contrast to other API endpoints, the response is not JSON, +but the raw XML output from `malloc_info(3)`. See also the +[glibc malloc(3) internals](https://sourceware.org/glibc/wiki/MallocInternals). + +```xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +``` + ## API Clients After its initial release in 2015, community members diff --git a/lib/remote/CMakeLists.txt b/lib/remote/CMakeLists.txt index 740b112b4..e2f9973df 100644 --- a/lib/remote/CMakeLists.txt +++ b/lib/remote/CMakeLists.txt @@ -32,6 +32,7 @@ set(remote_SOURCES infohandler.cpp infohandler.hpp jsonrpc.cpp jsonrpc.hpp jsonrpcconnection.cpp jsonrpcconnection.hpp jsonrpcconnection-heartbeat.cpp jsonrpcconnection-pki.cpp + mallocinfohandler.cpp mallocinfohandler.hpp messageorigin.cpp messageorigin.hpp modifyobjecthandler.cpp modifyobjecthandler.hpp objectqueryhandler.cpp objectqueryhandler.hpp diff --git a/lib/remote/mallocinfohandler.cpp b/lib/remote/mallocinfohandler.cpp new file mode 100644 index 000000000..ac73e3650 --- /dev/null +++ b/lib/remote/mallocinfohandler.cpp @@ -0,0 +1,94 @@ +/* Icinga 2 | (c) 2024 Icinga GmbH | GPLv2+ */ + +#include "base/defer.hpp" +#include "remote/filterutility.hpp" +#include "remote/httputility.hpp" +#include "remote/mallocinfohandler.hpp" +#include +#include +#include + +#ifdef HAVE_MALLOC_INFO +# include +# include +#endif /* HAVE_MALLOC_INFO */ + +using namespace icinga; + +REGISTER_URLHANDLER("/v1/debug/malloc_info", MallocInfoHandler); + +bool MallocInfoHandler::HandleRequest( + AsioTlsStream&, + const ApiUser::Ptr& user, + boost::beast::http::request& request, + const Url::Ptr& url, + boost::beast::http::response& response, + const Dictionary::Ptr& params, + boost::asio::yield_context&, + HttpServerConnection& +) +{ + namespace http = boost::beast::http; + + if (url->GetPath().size() != 3) { + return false; + } + + if (request.method() != http::verb::get) { + return false; + } + + FilterUtility::CheckPermission(user, "debug"); + +#ifndef HAVE_MALLOC_INFO + HttpUtility::SendJsonError(response, params, 501, "malloc_info(3) not available."); +#else /* HAVE_MALLOC_INFO */ + char* buf = nullptr; + size_t bufSize = 0; + FILE* f = nullptr; + + Defer release ([&f, &buf]() { + if (f) { + (void)fclose(f); + } + + free(buf); + }); + + f = open_memstream(&buf, &bufSize); + + if (!f) { + auto error (errno); + + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("open_memstream") + << boost::errinfo_errno(error)); + } + + if (malloc_info(0, f)) { + auto error (errno); + + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("malloc_info") + << boost::errinfo_errno(error)); + } + + auto closeErr (fclose(f)); + f = nullptr; + + if (closeErr) { + auto error (errno); + + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("fclose") + << boost::errinfo_errno(error)); + } + + response.result(200); + response.set(http::field::content_type, "application/xml"); + response.body() = std::string(buf, bufSize); + response.content_length(response.body().size()); +#endif /* HAVE_MALLOC_INFO */ + + return true; +} diff --git a/lib/remote/mallocinfohandler.hpp b/lib/remote/mallocinfohandler.hpp new file mode 100644 index 000000000..0e188f3eb --- /dev/null +++ b/lib/remote/mallocinfohandler.hpp @@ -0,0 +1,27 @@ +/* Icinga 2 | (c) 2024 Icinga GmbH | GPLv2+ */ + +#pragma once + +#include "remote/httphandler.hpp" + +namespace icinga +{ + +class MallocInfoHandler final : public HttpHandler +{ +public: + DECLARE_PTR_TYPEDEFS(MallocInfoHandler); + + bool HandleRequest( + AsioTlsStream& stream, + const ApiUser::Ptr& user, + boost::beast::http::request& request, + const Url::Ptr& url, + boost::beast::http::response& response, + const Dictionary::Ptr& params, + boost::asio::yield_context& yc, + HttpServerConnection& server + ) override; +}; + +} From ca7cc5443831c77778c35191fb0aa26f1944673f Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 29 Feb 2024 10:47:55 +0100 Subject: [PATCH 022/415] Checkable: Don't recalculate `next_check` while processing remotely genrated check Currently, when processing a `CheckResult`, it will first trigger an `OnNextCheckChanged` event, which is sent to all connected endpoints. Then, when `Checkable::ProcessCheckResult()` returns, an `OnCheckResult` event is fired, which is of course also sent to all connected endpoints. Next, the other endpoints receive the `event::SetNextCheck` cluster event followed by `event::CheckResult`and invoke `checkable#SetNextCheck()` and `Checkable#CheckResult()` with the newly received check. So they also try to recalculate the next check themselves and invalidate the previously received next check timestamp from the source endpoint. Since each endpoint randomly initialises its own scheduling offset, the recalculated next check will always differ by a split second/millisecond on each of them. As a consequence, two Icinga DB HA instances will generate two different checksums for the same state and causes the state histories to be fully resynchronised after a takeover/Icinga 2 reload. --- lib/icinga/checkable-check.cpp | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/lib/icinga/checkable-check.cpp b/lib/icinga/checkable-check.cpp index efa9477a2..f0af81bdd 100644 --- a/lib/icinga/checkable-check.cpp +++ b/lib/icinga/checkable-check.cpp @@ -360,21 +360,28 @@ Checkable::ProcessingResult Checkable::ProcessCheckResult(const CheckResult::Ptr bool is_flapping = IsFlapping(); - if (cr->GetActive()) { - UpdateNextCheck(origin); - } else { - /* Reschedule the next check for external passive check results. The side effect of - * this is that for as long as we receive results for a service we - * won't execute any active checks. */ - double offset; - double ttl = cr->GetTtl(); + // Don't recompute the next check when the current check isn't generated by this endpoint. When the check is + // remotely generated we should've already received the "SetNextCheck" event before the "event::CheckResult" + // cluster event. Otherwise, the next check received before this check will be invalidated and cause the Checkable + // "next_check/next_update" in a HA setup to always be different from the other endpoint as the "m_SchedulingOffset" + // is randomly initialised on each node. + if (!origin) { + if (cr->GetActive()) { + UpdateNextCheck(); + } else { + /* Reschedule the next check for external passive check results. The side effect of + * this is that for as long as we receive results for a service we + * won't execute any active checks. */ + double offset; + double ttl = cr->GetTtl(); - if (ttl > 0) - offset = ttl; - else - offset = GetCheckInterval(); + if (ttl > 0) + offset = ttl; + else + offset = GetCheckInterval(); - SetNextCheck(Utility::GetTime() + offset, false, origin); + SetNextCheck(Utility::GetTime() + offset); + } } olock.Unlock(); From edb67308587c16149ee8e151202d53934bd30d15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Mon, 19 Aug 2024 16:49:12 +0200 Subject: [PATCH 023/415] doc/03-monitoring-basics.md: fix invalid link address --- doc/03-monitoring-basics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/03-monitoring-basics.md b/doc/03-monitoring-basics.md index 06ea0c166..e534b0d01 100644 --- a/doc/03-monitoring-basics.md +++ b/doc/03-monitoring-basics.md @@ -1599,7 +1599,7 @@ A common pattern is to store the users and user groups on the host or service objects instead of the notification object itself. -The sample configuration provided in [hosts.conf](04-configuration.md#hosts-conf) and [notifications.conf](notifications-conf) +The sample configuration provided in [hosts.conf](04-configuration.md#hosts-conf) and [notifications.conf](04-configuration.md#notifications-conf) already provides an example for this question. > **Tip** From 4fefdd0969e84015bd845940f81cd7798fd5b5e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Tue, 20 Aug 2024 18:13:42 +0200 Subject: [PATCH 024/415] doc/21-development.md: fix bad link address --- doc/21-development.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/21-development.md b/doc/21-development.md index 27387f235..cf7a54dd7 100644 --- a/doc/21-development.md +++ b/doc/21-development.md @@ -1935,7 +1935,7 @@ Download the [boost-binaries](https://sourceforge.net/projects/boost/files/boost - 64 for 64 bit builds ``` -https://sourceforge.net/projects/boost/files/boost-binaries/1.82.0/boost_1_85_0-msvc-14.2-64.exe/download +https://sourceforge.net/projects/boost/files/boost-binaries/1.85.0/boost_1_85_0-msvc-14.2-64.exe/download ``` Run the installer and leave the default installation path in `C:\local\boost_1_85_0`. From 584340a203d1d84fb9fcf2a3d1d9278ff5458d72 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 19 Aug 2024 16:11:59 +0200 Subject: [PATCH 025/415] Bump Boost shipped for Windows to v1.86 --- doc/win-dev.ps1 | 2 +- tools/win32/configure-dev.ps1 | 4 ++-- tools/win32/configure.ps1 | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/win-dev.ps1 b/doc/win-dev.ps1 index 06ff49b55..93e9d41b7 100644 --- a/doc/win-dev.ps1 +++ b/doc/win-dev.ps1 @@ -13,7 +13,7 @@ function ThrowOnNativeFailure { $VsVersion = 2019 $MsvcVersion = '14.2' -$BoostVersion = @(1, 85, 0) +$BoostVersion = @(1, 86, 0) $OpensslVersion = '3_0_14' switch ($Env:BITS) { diff --git a/tools/win32/configure-dev.ps1 b/tools/win32/configure-dev.ps1 index 40b381e42..12cc004c8 100644 --- a/tools/win32/configure-dev.ps1 +++ b/tools/win32/configure-dev.ps1 @@ -31,10 +31,10 @@ if (-not (Test-Path env:OPENSSL_ROOT_DIR)) { $env:OPENSSL_ROOT_DIR = 'c:\local\OpenSSL-Win64' } if (-not (Test-Path env:BOOST_ROOT)) { - $env:BOOST_ROOT = 'c:\local\boost_1_85_0' + $env:BOOST_ROOT = 'c:\local\boost_1_86_0' } if (-not (Test-Path env:BOOST_LIBRARYDIR)) { - $env:BOOST_LIBRARYDIR = 'c:\local\boost_1_85_0\lib64-msvc-14.2' + $env:BOOST_LIBRARYDIR = 'c:\local\boost_1_86_0\lib64-msvc-14.2' } if (-not (Test-Path env:FLEX_BINARY)) { $env:FLEX_BINARY = 'C:\ProgramData\chocolatey\bin\win_flex.exe' diff --git a/tools/win32/configure.ps1 b/tools/win32/configure.ps1 index 90b4d9332..9871d0e8e 100644 --- a/tools/win32/configure.ps1 +++ b/tools/win32/configure.ps1 @@ -33,10 +33,10 @@ if (-not (Test-Path env:OPENSSL_ROOT_DIR)) { $env:OPENSSL_ROOT_DIR = "c:\local\OpenSSL_3_0_14-Win${env:BITS}" } if (-not (Test-Path env:BOOST_ROOT)) { - $env:BOOST_ROOT = "c:\local\boost_1_85_0-Win${env:BITS}" + $env:BOOST_ROOT = "c:\local\boost_1_86_0-Win${env:BITS}" } if (-not (Test-Path env:BOOST_LIBRARYDIR)) { - $env:BOOST_LIBRARYDIR = "c:\local\boost_1_85_0-Win${env:BITS}\lib${env:BITS}-msvc-14.2" + $env:BOOST_LIBRARYDIR = "c:\local\boost_1_86_0-Win${env:BITS}\lib${env:BITS}-msvc-14.2" } if (-not (Test-Path env:FLEX_BINARY)) { $env:FLEX_BINARY = 'C:\ProgramData\chocolatey\bin\win_flex.exe' From 81607426a2fbd970a0785426673d7d6b479a1b3b Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 21 Aug 2024 11:48:19 +0200 Subject: [PATCH 026/415] .deb: let user install icinga-archive-keyring package --- doc/02-installation.md | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/doc/02-installation.md b/doc/02-installation.md index 79741769d..982e91826 100644 --- a/doc/02-installation.md +++ b/doc/02-installation.md @@ -31,9 +31,13 @@ Here's how to add it to your system: ```bash apt update -apt -y install apt-transport-https wget gnupg +apt -y install apt-transport-https wget -wget -O - https://packages.icinga.com/icinga.key | gpg --dearmor -o /usr/share/keyrings/icinga-archive-keyring.gpg +wget -O icinga-archive-keyring.deb "https://packages.icinga.com/icinga-archive-keyring_latest+debian$( + . /etc/os-release; echo "$VERSION_ID" +).deb" + +apt install ./icinga-archive-keyring.deb DIST=$(awk -F"[)(]+" '/VERSION=/ {print $2}' /etc/os-release); \ echo "deb [signed-by=/usr/share/keyrings/icinga-archive-keyring.gpg] https://packages.icinga.com/debian icinga-${DIST} main" > \ @@ -65,9 +69,13 @@ apt update ```bash apt update -apt -y install apt-transport-https wget gnupg +apt -y install apt-transport-https wget -wget -O - https://packages.icinga.com/icinga.key | gpg --dearmor -o /usr/share/keyrings/icinga-archive-keyring.gpg +wget -O icinga-archive-keyring.deb "https://packages.icinga.com/icinga-archive-keyring_latest+ubuntu$( + . /etc/os-release; echo "$VERSION_ID" +).deb" + +apt install ./icinga-archive-keyring.deb . /etc/os-release; if [ ! -z ${UBUNTU_CODENAME+x} ]; then DIST="${UBUNTU_CODENAME}"; else DIST="$(lsb_release -c| awk '{print $2}')"; fi; \ echo "deb [signed-by=/usr/share/keyrings/icinga-archive-keyring.gpg] https://packages.icinga.com/ubuntu icinga-${DIST} main" > \ From aab0952bb175392a670a857a454fff537e1536f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Wed, 21 Aug 2024 12:09:27 +0200 Subject: [PATCH 027/415] doc/02-installation.md: remove outdated info The Backports Repository is required for Debian 9, but we don't build even v10 anymore. --- doc/02-installation.md | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/doc/02-installation.md b/doc/02-installation.md index 79741769d..34023727b 100644 --- a/doc/02-installation.md +++ b/doc/02-installation.md @@ -43,21 +43,6 @@ DIST=$(awk -F"[)(]+" '/VERSION=/ {print $2}' /etc/os-release); \ apt update ``` - -#### Debian Backports Repository - -This repository is required for Debian Stretch since Icinga v2.11. - -Debian Stretch: - -```bash -DIST=$(awk -F"[)(]+" '/VERSION=/ {print $2}' /etc/os-release); \ - echo "deb https://deb.debian.org/debian ${DIST}-backports main" > \ - /etc/apt/sources.list.d/${DIST}-backports.list - -apt update -``` - From 14e269060ae78dd44677605d4d2d94c09543e6ea Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 20 Jun 2024 17:12:21 +0200 Subject: [PATCH 028/415] Clarify that our Debian installation docs also apply to the Raspberry Pi OS --- .github/workflows/linux.yml | 4 ++-- doc/02-installation.md | 6 +++--- doc/02-installation.md.d/03-Raspberry-Pi-OS.md | 3 +++ doc/13-addons.md | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 doc/02-installation.md.d/03-Raspberry-Pi-OS.md diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index e47033898..19f87097d 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -23,8 +23,8 @@ jobs: distro: - amazonlinux:2 - amazonlinux:2023 - - debian:11 # and Raspbian 11 - - debian:12 # and Raspbian 12 + - debian:11 # and Raspberry Pi OS 11 + - debian:12 # and Raspberry Pi OS 12 - fedora:37 - fedora:38 - fedora:39 diff --git a/doc/02-installation.md b/doc/02-installation.md index 79741769d..f9b9087e5 100644 --- a/doc/02-installation.md +++ b/doc/02-installation.md @@ -246,7 +246,7 @@ with `root` permissions unless noted otherwise. -#### Debian / Ubuntu / Raspbian +#### Debian / Ubuntu / Raspbian / Raspberry Pi OS ```bash apt install icinga2 @@ -358,7 +358,7 @@ to determine where to find the plugin binaries. -#### Debian / Ubuntu / Raspbian +#### Debian / Ubuntu / Raspbian / Raspberry Pi OS ```bash apt install monitoring-plugins @@ -531,7 +531,7 @@ yum install icingadb-redis -##### Debian / Ubuntu +##### Debian / Ubuntu / Raspberry Pi OS ```bash apt install icingadb-redis diff --git a/doc/02-installation.md.d/03-Raspberry-Pi-OS.md b/doc/02-installation.md.d/03-Raspberry-Pi-OS.md new file mode 100644 index 000000000..a69b453ee --- /dev/null +++ b/doc/02-installation.md.d/03-Raspberry-Pi-OS.md @@ -0,0 +1,3 @@ +# Install Icinga 2 on Raspberry Pi OS + + diff --git a/doc/13-addons.md b/doc/13-addons.md index 953b7f0bb..c3823f53d 100644 --- a/doc/13-addons.md +++ b/doc/13-addons.md @@ -32,7 +32,7 @@ vim /etc/icinga2/conf.d/templates.conf Install the package `nano-icinga2` with your distribution's package manager. -**Note:** On Debian, Ubuntu and Raspbian, the syntax files are installed with the `icinga2-common` package already. +**Note:** On Debian, Ubuntu, Raspbian and Raspberry Pi OS, the syntax files are installed with the `icinga2-common` package already. Copy the `/etc/nanorc` sample file to your home directory. From d6bb971c7f8054a3ac1d394dbe96fc02d798b150 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 21 Aug 2024 11:24:14 +0200 Subject: [PATCH 029/415] doc/02-installation.md: remove Raspbian which is not supported anymore --- doc/02-installation.md | 27 ++++--------------------- doc/02-installation.md.d/03-Raspbian.md | 3 --- doc/13-addons.md | 2 +- 3 files changed, 5 insertions(+), 27 deletions(-) delete mode 100644 doc/02-installation.md.d/03-Raspbian.md diff --git a/doc/02-installation.md b/doc/02-installation.md index f9b9087e5..9abd4b66e 100644 --- a/doc/02-installation.md +++ b/doc/02-installation.md @@ -79,25 +79,6 @@ apt update ``` - -### Raspbian Repository - -```bash -apt update -apt -y install apt-transport-https wget gnupg - -wget -O - https://packages.icinga.com/icinga.key | gpg --dearmor -o /usr/share/keyrings/icinga-archive-keyring.gpg - -DIST=$(awk -F"[)(]+" '/VERSION=/ {print $2}' /etc/os-release); \ - echo "deb [signed-by=/usr/share/keyrings/icinga-archive-keyring.gpg] https://packages.icinga.com/raspbian icinga-${DIST} main" > \ - /etc/apt/sources.list.d/icinga.list - echo "deb-src [signed-by=/usr/share/keyrings/icinga-archive-keyring.gpg] https://packages.icinga.com/raspbian icinga-${DIST} main" >> \ - /etc/apt/sources.list.d/icinga.list - -apt update -``` - - ### CentOS Repository @@ -244,9 +225,9 @@ with `root` permissions unless noted otherwise. If you have [SELinux](22-selinux.md) enabled, the package `icinga2-selinux` is also required. - + -#### Debian / Ubuntu / Raspbian / Raspberry Pi OS +#### Debian / Ubuntu / Raspberry Pi OS ```bash apt install icinga2 @@ -356,9 +337,9 @@ to determine where to find the plugin binaries. additional check plugins into your Icinga 2 setup. - + -#### Debian / Ubuntu / Raspbian / Raspberry Pi OS +#### Debian / Ubuntu / Raspberry Pi OS ```bash apt install monitoring-plugins diff --git a/doc/02-installation.md.d/03-Raspbian.md b/doc/02-installation.md.d/03-Raspbian.md deleted file mode 100644 index fc48d6c67..000000000 --- a/doc/02-installation.md.d/03-Raspbian.md +++ /dev/null @@ -1,3 +0,0 @@ -# Install Icinga 2 on Raspbian - - diff --git a/doc/13-addons.md b/doc/13-addons.md index c3823f53d..f92982a94 100644 --- a/doc/13-addons.md +++ b/doc/13-addons.md @@ -32,7 +32,7 @@ vim /etc/icinga2/conf.d/templates.conf Install the package `nano-icinga2` with your distribution's package manager. -**Note:** On Debian, Ubuntu, Raspbian and Raspberry Pi OS, the syntax files are installed with the `icinga2-common` package already. +**Note:** On Debian, Ubuntu and Raspberry Pi OS, the syntax files are installed with the `icinga2-common` package already. Copy the `/etc/nanorc` sample file to your home directory. From 090dcfd70f91030f54998cabef0dfb10d122d9fa Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Wed, 21 Aug 2024 10:55:09 +0200 Subject: [PATCH 030/415] Add tests for Utility::FormatDateTime() --- test/CMakeLists.txt | 1 + test/base-utility.cpp | 49 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 28b528463..e3772886a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -128,6 +128,7 @@ add_boost_test(base base_utility/validateutf8 base_utility/EscapeCreateProcessArg base_utility/TruncateUsingHash + base_utility/FormatDateTime base_value/scalar base_value/convert base_value/format diff --git a/test/base-utility.cpp b/test/base-utility.cpp index 65222e1fd..77d91f47a 100644 --- a/test/base-utility.cpp +++ b/test/base-utility.cpp @@ -135,4 +135,53 @@ BOOST_AUTO_TEST_CASE(TruncateUsingHash) std::string(37, 'a') + "...86f33652fcffd7fa1443e246dd34fe5d00e25ffd"); } +BOOST_AUTO_TEST_CASE(FormatDateTime) { + using time_t_limit = std::numeric_limits; + + // Helper to repeat a given string a number of times. + auto repeat = [](const std::string& s, size_t n) { + std::ostringstream stream; + for (size_t i = 0; i < n; ++i) { + stream << s; + } + return stream.str(); + }; + + // Valid inputs. + const double ts = 1136214245.0; // 2006-01-02 15:04:05 UTC + BOOST_CHECK_EQUAL("2006-01-02 15:04:05", Utility::FormatDateTime("%F %T", ts)); + BOOST_CHECK_EQUAL("2006", Utility::FormatDateTime("%Y", ts)); + BOOST_CHECK_EQUAL("2006#2006", Utility::FormatDateTime("%Y#%Y", ts)); + BOOST_CHECK_EQUAL("%", Utility::FormatDateTime("%%", ts)); + BOOST_CHECK_EQUAL("%Y", Utility::FormatDateTime("%%Y", ts)); + BOOST_CHECK_EQUAL("", Utility::FormatDateTime("", ts)); + BOOST_CHECK_EQUAL("1970-01-01 00:00:00", Utility::FormatDateTime("%F %T", 0.0)); + BOOST_CHECK_EQUAL("2038-01-19 03:14:07", Utility::FormatDateTime("%F %T", 2147483647.0)); // 2^31 - 1 + if constexpr (sizeof(time_t) > sizeof(int32_t)) { + BOOST_CHECK_EQUAL("2100-03-14 13:37:42", Utility::FormatDateTime("%F %T", 4108714662.0)); // Past year 2038 + } else { + BOOST_WARN_MESSAGE(false, "skipping test with past 2038 input due to 32 bit time_t"); + } + + // Negative (pre-1970) timestamps. +#ifdef _MSC_VER + // localtime_s() on Windows doesn't seem to like them and always errors out. + BOOST_CHECK_THROW(Utility::FormatDateTime("%F %T", -1.0), posix_error); + BOOST_CHECK_THROW(Utility::FormatDateTime("%F %T", -2147483648.0), posix_error); // -2^31 +#else /* _MSC_VER */ + BOOST_CHECK_EQUAL("1969-12-31 23:59:59", Utility::FormatDateTime("%F %T", -1.0)); + BOOST_CHECK_EQUAL("1901-12-13 20:45:52", Utility::FormatDateTime("%F %T", -2147483648.0)); // -2^31 +#endif /* _MSC_VER */ + + // Values right at the limits of time_t. + // + // With 64 bit time_t, there may not be an exact double representation of its min/max value, std::nextafter() is + // used to move the value towards 0 so that it's within the range of doubles that can be represented as time_t. + // + // These are expected to result in an error due to the intermediate struct tm not being able to represent these + // timestamps, so localtime_r() returns EOVERFLOW which makes the implementation throw an exception. + BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", std::nextafter(time_t_limit::min(), 0)), posix_error); + BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", std::nextafter(time_t_limit::max(), 0)), posix_error); +} + BOOST_AUTO_TEST_SUITE_END() From 704acdc6985b558dd148885fc78946072e83b568 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Wed, 21 Aug 2024 11:15:34 +0200 Subject: [PATCH 031/415] Utility::FormatDateTime(): use boost::numeric_cast<>() The previous implementation actually had undefined behavior when called with a double that can't be represented as time_t. With boost::numeric_cast, there's a convenient cast available that avoids this and throws an exceptions on overflow. It's undefined behavior ([0], where the implicit conversion rule comes into play because the C-style cast uses static_cast [1] which in turn uses the imlicit conversion as per rule 5 of [2]): > A prvalue of floating-point type can be converted to a prvalue of any integer > type. The fractional part is truncated, that is, the fractional part is > discarded. > > * If the truncated value cannot fit into the destination type, the behavior > is undefined (even when the destination type is unsigned, modulo arithmetic > does not apply). Note that on Linux amd64, the undefined behavior typically manifests itself in the result being the minimal value of time_t which then results in localtime_r failing with EOVERFLOW. [0]: https://en.cppreference.com/w/cpp/language/implicit_conversion#Floating.E2.80.93integral_conversions [1]: https://en.cppreference.com/w/cpp/language/explicit_cast [2]: https://en.cppreference.com/w/cpp/language/static_cast --- lib/base/utility.cpp | 4 +++- test/base-utility.cpp | 7 +++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/base/utility.cpp b/lib/base/utility.cpp index 6ff84ae65..6f272178a 100644 --- a/lib/base/utility.cpp +++ b/lib/base/utility.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -1052,7 +1053,8 @@ String Utility::FormatDuration(double duration) String Utility::FormatDateTime(const char *format, double ts) { char timestamp[128]; - auto tempts = (time_t)ts; /* We don't handle sub-second timestamps here just yet. */ + // Sub-second precision is removed, strftime() has no format specifiers for that anyway. + auto tempts = boost::numeric_cast(ts); tm tmthen; #ifdef _MSC_VER diff --git a/test/base-utility.cpp b/test/base-utility.cpp index 77d91f47a..ced81ae4a 100644 --- a/test/base-utility.cpp +++ b/test/base-utility.cpp @@ -137,6 +137,9 @@ BOOST_AUTO_TEST_CASE(TruncateUsingHash) BOOST_AUTO_TEST_CASE(FormatDateTime) { using time_t_limit = std::numeric_limits; + using double_limit = std::numeric_limits; + using boost::numeric::negative_overflow; + using boost::numeric::positive_overflow; // Helper to repeat a given string a number of times. auto repeat = [](const std::string& s, size_t n) { @@ -182,6 +185,10 @@ BOOST_AUTO_TEST_CASE(FormatDateTime) { // timestamps, so localtime_r() returns EOVERFLOW which makes the implementation throw an exception. BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", std::nextafter(time_t_limit::min(), 0)), posix_error); BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", std::nextafter(time_t_limit::max(), 0)), posix_error); + + // Out of range timestamps. + BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", std::nextafter(time_t_limit::min(), -double_limit::infinity())), negative_overflow); + BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", std::nextafter(time_t_limit::max(), +double_limit::infinity())), positive_overflow); } BOOST_AUTO_TEST_SUITE_END() From c2c66908f61ef3df8e6bffda0d348d447356d974 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Wed, 21 Aug 2024 11:43:13 +0200 Subject: [PATCH 032/415] Utility::FormatDateTime(): use localtime_s() on Windows localtime() is not thread-safe as it returns a pointer to a shared tm struct. Everywhere except on Windows, localtime_r() is used already which avoids the problem by using a struct allocated by the caller for the output. Windows actually has a similar function called localtime_s() which has the same properties, just with a different name and order of arguments. --- lib/base/utility.cpp | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/lib/base/utility.cpp b/lib/base/utility.cpp index 6f272178a..6985ce539 100644 --- a/lib/base/utility.cpp +++ b/lib/base/utility.cpp @@ -1058,15 +1058,12 @@ String Utility::FormatDateTime(const char *format, double ts) tm tmthen; #ifdef _MSC_VER - tm *temp = localtime(&tempts); - - if (!temp) { + errno_t err = localtime_s(&tmthen, &tempts); + if (err) { BOOST_THROW_EXCEPTION(posix_error() - << boost::errinfo_api_function("localtime") - << boost::errinfo_errno(errno)); + << boost::errinfo_api_function("localtime_s") + << boost::errinfo_errno(err)); } - - tmthen = *temp; #else /* _MSC_VER */ if (!localtime_r(&tempts, &tmthen)) { BOOST_THROW_EXCEPTION(posix_error() From 0285028689f91194f4a8392bef239520b829b8c1 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Wed, 21 Aug 2024 11:55:19 +0200 Subject: [PATCH 033/415] Utility::FormatDateTime(): handle errors from strftime() So far, the return value of strftime() was simply ignored and the output buffer passed to the icinga::String constructor. However, there are error conditions where strftime() returns 0 to signal an error, like if the buffer was too small for the output. In that case, there's no guarantee on the buffer contents and reading it can result in undefined behavior. Unfortunately, returning 0 can also indicate success and strftime() doesn't set errno, so there's no reliable way to distinguish both situations. Thus, the implementation now returns the empty string in both cases. I attempted to use std::put_time() at first as that allows for better error handling, however, there were problems with the implementation on Windows (see inline comment), so I put that plan on hold at left strftime() there for the time being. --- lib/base/utility.cpp | 29 +++++++++++++++++++++++++---- test/base-utility.cpp | 5 +++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/lib/base/utility.cpp b/lib/base/utility.cpp index 6985ce539..e1bc42b8d 100644 --- a/lib/base/utility.cpp +++ b/lib/base/utility.cpp @@ -1052,7 +1052,27 @@ String Utility::FormatDuration(double duration) String Utility::FormatDateTime(const char *format, double ts) { - char timestamp[128]; + /* Known limitations of the implementation: Only works if the result is at most 127 bytes, otherwise returns an + * empty string. An empty string is also returned in all other error cases as proper error handling for strftime() + * is impossible. + * + * From strftime(3): + * + * If the output string would exceed max bytes, errno is not set. This makes it impossible to distinguish this + * error case from cases where the format string legitimately produces a zero-length output string. POSIX.1-2001 + * does not specify any errno settings for strftime(). + * + * https://manpages.debian.org/bookworm/manpages-dev/strftime.3.en.html#BUGS + * + * There's also std::put_time() from C++ which works with an ostream and does not have a fixed size output buffer + * and should allow using the error handling of the ostream. However, there seem to be an unfortunate implementation + * of this on some Windows versions where passing an invalid format string results in std::bad_alloc and the process + * allocating more and more memory before throwing the exception. In case someone in the future wants to try + * std::put_time() again: better build packages for Windows and test them across all supported versions. + * Hypothesis: it's implemented using a fixed output buffer and retrying with a larger buffer on error, assuming + * the error was due to the buffer being too small. + */ + // Sub-second precision is removed, strftime() has no format specifiers for that anyway. auto tempts = boost::numeric_cast(ts); tm tmthen; @@ -1072,9 +1092,10 @@ String Utility::FormatDateTime(const char *format, double ts) } #endif /* _MSC_VER */ - strftime(timestamp, sizeof(timestamp), format, &tmthen); - - return timestamp; + char buf[128]; + size_t n = strftime(buf, sizeof(buf), format, &tmthen); + // On error, n == 0 and an empty string is returned. + return std::string(buf, n); } String Utility::FormatErrorNumber(int code) { diff --git a/test/base-utility.cpp b/test/base-utility.cpp index ced81ae4a..5c0d358cd 100644 --- a/test/base-utility.cpp +++ b/test/base-utility.cpp @@ -186,6 +186,11 @@ BOOST_AUTO_TEST_CASE(FormatDateTime) { BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", std::nextafter(time_t_limit::min(), 0)), posix_error); BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", std::nextafter(time_t_limit::max(), 0)), posix_error); + // Excessive format strings can result in something too large for the buffer, errors out to the empty string. + // Note: both returning the proper result or throwing an exception would be fine too, unfortunately, that's + // not really possible due to limitations in strftime() error handling, see comment in the implementation. + BOOST_CHECK_EQUAL("", Utility::FormatDateTime(repeat("%Y", 1000).c_str(), ts)); + // Out of range timestamps. BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", std::nextafter(time_t_limit::min(), -double_limit::infinity())), negative_overflow); BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", std::nextafter(time_t_limit::max(), +double_limit::infinity())), positive_overflow); From d5b3ffaa6dee8cfacbc1b03881f04bbcc943c6d7 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Wed, 21 Aug 2024 12:08:04 +0200 Subject: [PATCH 034/415] Utility::FormatDateTime(): handle invalid format strings on Windows On Windows, the strftime() function family invokes an invalid parameter handler when the format string is invalid (see the "Remarks" section in their documentation). std::put_time() shows the same behavior as it uses _wcsftime_l() internally. The default invalid parameter handler may terminate the process, which can be a problem given that the format string can be specified by the user from the Icinga DSL. Thus, temporarily set a thread-local no-op handler to disable the default one allowing the program to continue. This then simply results in the function returning an error which then results in an exception as we ask the stream to throw one. See also: https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/strftime-wcsftime-strftime-l-wcsftime-l?view=msvc-170 https://learn.microsoft.com/en-us/cpp/c-runtime-library/parameter-validation?view=msvc-170 https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/set-invalid-parameter-handler-set-thread-local-invalid-parameter-handler?view=msvc-170 --- lib/base/utility.cpp | 27 +++++++++++++++++++++++++++ test/base-utility.cpp | 13 +++++++++++++ 2 files changed, 40 insertions(+) diff --git a/lib/base/utility.cpp b/lib/base/utility.cpp index e1bc42b8d..75b905fe1 100644 --- a/lib/base/utility.cpp +++ b/lib/base/utility.cpp @@ -4,6 +4,7 @@ #include "base/utility.hpp" #include "base/convert.hpp" #include "base/application.hpp" +#include "base/defer.hpp" #include "base/logger.hpp" #include "base/exception.hpp" #include "base/socket.hpp" @@ -1092,6 +1093,32 @@ String Utility::FormatDateTime(const char *format, double ts) } #endif /* _MSC_VER */ +#ifdef _MSC_VER + /* On Windows, the strftime() function family invokes an invalid parameter handler when the format string is + * invalid (see the "Remarks" section in their documentation). std::put_time() shows the same behavior as it + * uses _wcsftime_l() internally. The default invalid parameter handler may terminate the process, which can + * be a problem given that the format string can be specified by the user from the Icinga DSL. + * + * Thus, temporarily set a thread-local no-op handler to disable the default one allowing the program to + * continue. This then simply results in the function returning an error which then results in an exception as + * we ask the stream to throw one. + * + * See also: + * https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/strftime-wcsftime-strftime-l-wcsftime-l?view=msvc-170 + * https://learn.microsoft.com/en-us/cpp/c-runtime-library/parameter-validation?view=msvc-170 + * https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/set-invalid-parameter-handler-set-thread-local-invalid-parameter-handler?view=msvc-170 + */ + + auto oldHandler = _set_thread_local_invalid_parameter_handler( + [](const wchar_t*, const wchar_t*, const wchar_t*, unsigned int, uintptr_t) { + // Intentionally do nothing to continue executing. + }); + + Defer resetHandler([oldHandler]() { + _set_thread_local_invalid_parameter_handler(oldHandler); + }); +#endif /* _MSC_VER */ + char buf[128]; size_t n = strftime(buf, sizeof(buf), format, &tmthen); // On error, n == 0 and an empty string is returned. diff --git a/test/base-utility.cpp b/test/base-utility.cpp index 5c0d358cd..8fe1814da 100644 --- a/test/base-utility.cpp +++ b/test/base-utility.cpp @@ -191,6 +191,19 @@ BOOST_AUTO_TEST_CASE(FormatDateTime) { // not really possible due to limitations in strftime() error handling, see comment in the implementation. BOOST_CHECK_EQUAL("", Utility::FormatDateTime(repeat("%Y", 1000).c_str(), ts)); + // Invalid format strings. + for (const char* format : {"%", "x % y", "x %! y"}) { + std::string result = Utility::FormatDateTime(format, ts); + + // Implementations of strftime() seem to either keep invalid format specifiers and return them in the output, or + // treat them as an error which our implementation currently maps to the empty string due to strftime() not + // properly reporting errors. If this limitation of our implementation is lifted, other behavior like throwing + // an exception would also be valid. + BOOST_CHECK_MESSAGE(result.empty() || result == format, + "FormatDateTime(" << std::quoted(format) << ", " << ts << ") = " << std::quoted(result) << + " should be one of [\"\", " << std::quoted(format) << "]"); + } + // Out of range timestamps. BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", std::nextafter(time_t_limit::min(), -double_limit::infinity())), negative_overflow); BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", std::nextafter(time_t_limit::max(), +double_limit::infinity())), positive_overflow); From 39ae2e8ca4eca6be1c73a70bdd0c45258a527613 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Wed, 21 Aug 2024 12:31:44 +0200 Subject: [PATCH 035/415] Utility::FormatDateTime(): provide an overload for tm* This allows the function to be used both with a double timestamp or a pointer to a tm struct. With this, a similar implementation inside the tests can simply use our regular function. --- lib/base/utility.cpp | 46 +++++++++++++++++--------------- lib/base/utility.hpp | 3 ++- test/icinga-legacytimeperiod.cpp | 11 +------- 3 files changed, 28 insertions(+), 32 deletions(-) diff --git a/lib/base/utility.cpp b/lib/base/utility.cpp index 75b905fe1..338e4f77f 100644 --- a/lib/base/utility.cpp +++ b/lib/base/utility.cpp @@ -1051,8 +1051,31 @@ String Utility::FormatDuration(double duration) return NaturalJoin(tokens); } -String Utility::FormatDateTime(const char *format, double ts) +String Utility::FormatDateTime(const char* format, double ts) { + // Sub-second precision is removed, strftime() has no format specifiers for that anyway. + auto tempts = boost::numeric_cast(ts); + tm tmthen; + +#ifdef _MSC_VER + errno_t err = localtime_s(&tmthen, &tempts); + if (err) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("localtime_s") + << boost::errinfo_errno(err)); + } +#else /* _MSC_VER */ + if (!localtime_r(&tempts, &tmthen)) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("localtime_r") + << boost::errinfo_errno(errno)); + } +#endif /* _MSC_VER */ + + return FormatDateTime(format, &tmthen); +} + +String Utility::FormatDateTime(const char* format, const tm* t) { /* Known limitations of the implementation: Only works if the result is at most 127 bytes, otherwise returns an * empty string. An empty string is also returned in all other error cases as proper error handling for strftime() * is impossible. @@ -1074,25 +1097,6 @@ String Utility::FormatDateTime(const char *format, double ts) * the error was due to the buffer being too small. */ - // Sub-second precision is removed, strftime() has no format specifiers for that anyway. - auto tempts = boost::numeric_cast(ts); - tm tmthen; - -#ifdef _MSC_VER - errno_t err = localtime_s(&tmthen, &tempts); - if (err) { - BOOST_THROW_EXCEPTION(posix_error() - << boost::errinfo_api_function("localtime_s") - << boost::errinfo_errno(err)); - } -#else /* _MSC_VER */ - if (!localtime_r(&tempts, &tmthen)) { - BOOST_THROW_EXCEPTION(posix_error() - << boost::errinfo_api_function("localtime_r") - << boost::errinfo_errno(errno)); - } -#endif /* _MSC_VER */ - #ifdef _MSC_VER /* On Windows, the strftime() function family invokes an invalid parameter handler when the format string is * invalid (see the "Remarks" section in their documentation). std::put_time() shows the same behavior as it @@ -1120,7 +1124,7 @@ String Utility::FormatDateTime(const char *format, double ts) #endif /* _MSC_VER */ char buf[128]; - size_t n = strftime(buf, sizeof(buf), format, &tmthen); + size_t n = strftime(buf, sizeof(buf), format, t); // On error, n == 0 and an empty string is returned. return std::string(buf, n); } diff --git a/lib/base/utility.hpp b/lib/base/utility.hpp index 47b68d251..5132673ca 100644 --- a/lib/base/utility.hpp +++ b/lib/base/utility.hpp @@ -77,7 +77,8 @@ public: static String Join(const Array::Ptr& tokens, char separator, bool escapeSeparator = true); static String FormatDuration(double duration); - static String FormatDateTime(const char *format, double ts); + static String FormatDateTime(const char* format, double ts); + static String FormatDateTime(const char* format, const tm* t); static String FormatErrorNumber(int code); #ifndef _WIN32 diff --git a/test/icinga-legacytimeperiod.cpp b/test/icinga-legacytimeperiod.cpp index 900bb6578..8661b75f7 100644 --- a/test/icinga-legacytimeperiod.cpp +++ b/test/icinga-legacytimeperiod.cpp @@ -527,16 +527,7 @@ struct Segment std::string pretty_time(const tm& t) { -#if defined(__GNUC__) && __GNUC__ < 5 - // GCC did not implement std::put_time() until version 5 - char buf[128]; - size_t n = strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S %Z", &t); - return std::string(buf, n); -#else /* defined(__GNUC__) && __GNUC__ < 5 */ - std::ostringstream stream; - stream << std::put_time(&t, "%Y-%m-%d %H:%M:%S %Z"); - return stream.str(); -#endif /* defined(__GNUC__) && __GNUC__ < 5 */ + return Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %Z", &t); } std::string pretty_time(time_t t) From f96e7c67ee3a652aeb781e840e084fb742e848cf Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 23 Aug 2024 12:38:49 +0200 Subject: [PATCH 036/415] On Windows, don't create C:\Program Files\Icinga2\var during MSI build --- icinga-app/CMakeLists.txt | 8 +++++--- lib/base/CMakeLists.txt | 6 ++++-- lib/remote/CMakeLists.txt | 16 ++++++++-------- 3 files changed, 17 insertions(+), 13 deletions(-) diff --git a/icinga-app/CMakeLists.txt b/icinga-app/CMakeLists.txt index ef71ad999..1c92d8331 100644 --- a/icinga-app/CMakeLists.txt +++ b/icinga-app/CMakeLists.txt @@ -95,6 +95,8 @@ install( RUNTIME DESTINATION ${InstallPath} ) -install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_LOGDIR}\")") -install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_DATADIR}\")") -install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_INITRUNDIR}\")") +if(NOT WIN32) + install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_LOGDIR}\")") + install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_DATADIR}\")") + install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_INITRUNDIR}\")") +endif() diff --git a/lib/base/CMakeLists.txt b/lib/base/CMakeLists.txt index e50e330e4..986050017 100644 --- a/lib/base/CMakeLists.txt +++ b/lib/base/CMakeLists.txt @@ -154,7 +154,9 @@ set_target_properties ( FOLDER Lib ) -install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_CACHEDIR}\")") -install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_LOGDIR}/crash\")") +if(NOT WIN32) + install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_CACHEDIR}\")") + install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_LOGDIR}/crash\")") +endif() set(CPACK_NSIS_EXTRA_INSTALL_COMMANDS "${CPACK_NSIS_EXTRA_INSTALL_COMMANDS}" PARENT_SCOPE) diff --git a/lib/remote/CMakeLists.txt b/lib/remote/CMakeLists.txt index e2f9973df..2271abff6 100644 --- a/lib/remote/CMakeLists.txt +++ b/lib/remote/CMakeLists.txt @@ -58,11 +58,11 @@ set_target_properties ( FOLDER Lib ) -#install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_DATADIR}/api\")") -install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_DATADIR}/api/log\")") -install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_DATADIR}/api/zones\")") -install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_DATADIR}/api/zones-stage\")") -install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_DATADIR}/certs\")") -install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_DATADIR}/certificate-requests\")") - - +if(NOT WIN32) + #install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_DATADIR}/api\")") + install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_DATADIR}/api/log\")") + install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_DATADIR}/api/zones\")") + install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_DATADIR}/api/zones-stage\")") + install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_DATADIR}/certs\")") + install(CODE "file(MAKE_DIRECTORY \"\$ENV{DESTDIR}${ICINGA2_FULL_DATADIR}/certificate-requests\")") +endif() From 91b1638a013194c53c5b61bedb5678ebb91b0c28 Mon Sep 17 00:00:00 2001 From: Nicolas Berens Date: Thu, 18 Jan 2024 10:58:32 +0100 Subject: [PATCH 037/415] add multiplier option to check_snmp which is available since monitoring-plugins v2.3.3, refs #9907 --- itl/command-plugins.conf | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/itl/command-plugins.conf b/itl/command-plugins.conf index 83b9c6167..9bdf33bfe 100644 --- a/itl/command-plugins.conf +++ b/itl/command-plugins.conf @@ -1770,6 +1770,10 @@ object CheckCommand "snmp" { value = "$snmp_miblist$" description = "List of MIBS to be loaded (default = none if using numeric OIDs or 'ALL' for symbolic OIDs.)" } + "-M" = { + value = "$snmp_multiplier$" + description = "Multiplies current value, 0 < n < 1 works as divider, defaults to 1" + } "--rate-multiplier" = { value = "$snmp_rate_multiplier$" description = "Converts rate per second. For example, set to 60 to convert to per minute" @@ -1898,6 +1902,10 @@ object CheckCommand "snmpv3" { value = "$snmpv3_miblist$" description = "List of SNMP MIBs for translating OIDs between numeric and textual representation" } + "-M" = { + value = "$snmpv3_multiplier$" + description = "Multiplies current value, 0 < n < 1 works as divider, defaults to 1" + } "-u" = { value = "$snmpv3_units$" description = "Units label(s) for output data (e.g., 'sec.')" From c76cade9ae7cef310692d689ca562ef57cab79dd Mon Sep 17 00:00:00 2001 From: Nicolas Berens Date: Wed, 29 May 2024 13:07:30 +0200 Subject: [PATCH 038/415] add new variables to documentation --- doc/10-icinga-template-library.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md index 2c64779d3..5bb81a95a 100644 --- a/doc/10-icinga-template-library.md +++ b/doc/10-icinga-template-library.md @@ -1322,6 +1322,7 @@ snmp_invert_search | **Optional.** Invert search result and return CRITICAL sta snmp_units | **Optional.** Units label(s) for output value (e.g., 'sec.'). snmp_version | **Optional.** Version to use. E.g. 1, 2, 2c or 3. snmp_miblist | **Optional.** MIB's to use, comma separated. Defaults to "ALL". +snmp_multiplier |**Optional.** Multiplies current value, 0 < n < 1 works as divider, defaults to 1 snmp_rate_multiplier | **Optional.** Converts rate per second. For example, set to 60 to convert to per minute. snmp_rate | **Optional.** Boolean. Enable rate calculation. snmp_getnext | **Optional.** Boolean. Use SNMP GETNEXT. Defaults to false. @@ -1357,6 +1358,7 @@ snmpv3_eregi | **Optional.** Return OK state (for that OID) if case-inse snmpv3_invert_search | **Optional.** Invert search result and return CRITICAL if found snmpv3_label | **Optional.** Prefix label for output value. snmpv3_units | **Optional.** Units label(s) for output value (e.g., 'sec.'). +snmp3_multiplier |**Optional.** Multiplies current value, 0 < n < 1 works as divider, defaults to 1 snmpv3_rate_multiplier | **Optional.** Converts rate per second. For example, set to 60 to convert to per minute. snmpv3_rate | **Optional.** Boolean. Enable rate calculation. snmpv3_timeout | **Optional.** The command timeout in seconds. Defaults to 10 seconds. From 600281bfa02115f7814cb17cdb8aa33d43bf1a7f Mon Sep 17 00:00:00 2001 From: Tobias Bauriedel Date: Thu, 18 Jan 2024 11:57:41 +0100 Subject: [PATCH 039/415] Fix ITL for http CheckCommand definition There were some missing arguments. ref/NC/806131 --- AUTHORS | 1 + doc/10-icinga-template-library.md | 2 ++ itl/command-plugins.conf | 9 +++++++++ 3 files changed, 12 insertions(+) diff --git a/AUTHORS b/AUTHORS index b661897b6..3253f3ec3 100644 --- a/AUTHORS +++ b/AUTHORS @@ -283,6 +283,7 @@ Thomas Widhalm Tim Hardeck Tim Weippert Timo Buhrmester +Tobias Bauriedel Tobias Birnbaum Tobias Deiminger Tobias von der Krone diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md index 5bb81a95a..f7b57b831 100644 --- a/doc/10-icinga-template-library.md +++ b/doc/10-icinga-template-library.md @@ -716,6 +716,7 @@ http_warn_time | **Optional.** The warning threshold. http_critical_time | **Optional.** The critical threshold. http_expect | **Optional.** Comma-delimited list of strings, at least one of them is expected in the first (status) line of the server response. Default: HTTP/1. http_certificate | **Optional.** Minimum number of days a certificate has to be valid. Port defaults to 443. When this option is used the URL is not checked. The first parameter defines the warning threshold (in days), the second parameter the critical threshold (in days). (Example `http_certificate = "30,20"`). +http_certificate_continue | **Optional.** Allows the HTTP check to continue after performing the certificate check. Does nothing unless http_certificate is used. http_clientcert | **Optional.** Name of file contains the client certificate (PEM format). http_privatekey | **Optional.** Name of file contains the private key (PEM format). http_headerstring | **Optional.** String to expect in the response headers. @@ -734,6 +735,7 @@ http_ipv4 | **Optional.** Use IPv4 connection. Defaults t http_ipv6 | **Optional.** Use IPv6 connection. Defaults to false. http_link | **Optional.** Wrap output in HTML link. Defaults to false. http_verbose | **Optional.** Show details for command-line debugging. Defaults to false. +http_extra_opts | **Optional.** Read extra plugin options from an ini file. http_verify_host | **Optional.** Verify SSL certificate is for the -H hostname (with --sni and -S). Defaults to false. **Only supported by the Nagios plugins version of check\_http, not by the monitoring plugins one.** diff --git a/itl/command-plugins.conf b/itl/command-plugins.conf index 9bdf33bfe..e80d1366d 100644 --- a/itl/command-plugins.conf +++ b/itl/command-plugins.conf @@ -360,6 +360,11 @@ object CheckCommand "http" { value = "$http_vhost$" description = "Host name argument for servers using host headers (virtual host)" } + "--extra-opts" = { + set_if = {{ string(macro("$http_extra_opts$")) != "" }} + value = "$http_extra_opts$" + description = "Read extra plugin options from an ini file" + } "-I" = { set_if = {{ string(macro("$http_address$")) != "" }} value = "$http_address$" @@ -425,6 +430,10 @@ object CheckCommand "http" { value = "$http_certificate$" description = "Minimum number of days a certificate has to be valid. This parameter explicitely sets the port to 443 and ignores the URL if passed." } + "--continue-after-certificate" = { + set_if = "$http_certificate_continue$" + description = "Allows the HTTP check to continue after performing the certificate check. Does nothing unless -C is used" + } "-J" = { value = "$http_clientcert$" description = "Name of file contains the client certificate (PEM format)" From 33f8ea6dcca34ffc8597f6e125bb3b400fa89750 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 7 Feb 2024 13:46:13 +0100 Subject: [PATCH 040/415] JsonRpcConnection#Disconnect(): spawn coroutine only if necessary by checking the now atomic #m_ShuttingDown outside of it. --- lib/remote/jsonrpcconnection.cpp | 12 +++++------- lib/remote/jsonrpcconnection.hpp | 3 ++- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/remote/jsonrpcconnection.cpp b/lib/remote/jsonrpcconnection.cpp index d5a8e46cb..9d3ca1795 100644 --- a/lib/remote/jsonrpcconnection.cpp +++ b/lib/remote/jsonrpcconnection.cpp @@ -188,12 +188,10 @@ void JsonRpcConnection::Disconnect() { namespace asio = boost::asio; - JsonRpcConnection::Ptr keepAlive (this); - - IoEngine::SpawnCoroutine(m_IoStrand, [this, keepAlive](asio::yield_context yc) { - if (!m_ShuttingDown) { - m_ShuttingDown = true; + if (!m_ShuttingDown.exchange(true)) { + JsonRpcConnection::Ptr keepAlive (this); + IoEngine::SpawnCoroutine(m_IoStrand, [this, keepAlive](asio::yield_context yc) { Log(LogWarning, "JsonRpcConnection") << "API client disconnected for identity '" << m_Identity << "'"; @@ -241,8 +239,8 @@ void JsonRpcConnection::Disconnect() shutdownTimeout->Cancel(); m_Stream->lowest_layer().shutdown(m_Stream->lowest_layer().shutdown_both, ec); - } - }); + }); + } } void JsonRpcConnection::MessageHandler(const String& jsonString) diff --git a/lib/remote/jsonrpcconnection.hpp b/lib/remote/jsonrpcconnection.hpp index 591ddcb1f..3515573bb 100644 --- a/lib/remote/jsonrpcconnection.hpp +++ b/lib/remote/jsonrpcconnection.hpp @@ -5,6 +5,7 @@ #include "remote/i2-remote.hpp" #include "remote/endpoint.hpp" +#include "base/atomic.hpp" #include "base/io-engine.hpp" #include "base/tlsstream.hpp" #include "base/timer.hpp" @@ -77,7 +78,7 @@ private: std::vector m_OutgoingMessagesQueue; AsioConditionVariable m_OutgoingMessagesQueued; AsioConditionVariable m_WriterDone; - bool m_ShuttingDown; + Atomic m_ShuttingDown; boost::asio::deadline_timer m_CheckLivenessTimer, m_HeartbeatTimer; JsonRpcConnection(const String& identity, bool authenticated, const Shared::Ptr& stream, ConnectionRole role, boost::asio::io_context& io); From b538ad2528e32258ad71be8ab3f8c0a9dc12802e Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 7 Feb 2024 13:56:31 +0100 Subject: [PATCH 041/415] JsonRpcConnection#Send*(): discard messages ASAP once shutting down Especially ApiListener#ReplayLog() enqueued lots of messages into JsonRpcConnection#{m_IoStrand,m_OutgoingMessagesQueue} (RAM) even if the connection was shut(ting) down. Now #Disconnect() takes effect ASAP. --- lib/remote/jsonrpcconnection.cpp | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/lib/remote/jsonrpcconnection.cpp b/lib/remote/jsonrpcconnection.cpp index 9d3ca1795..90aff0da6 100644 --- a/lib/remote/jsonrpcconnection.cpp +++ b/lib/remote/jsonrpcconnection.cpp @@ -163,6 +163,10 @@ ConnectionRole JsonRpcConnection::GetRole() const void JsonRpcConnection::SendMessage(const Dictionary::Ptr& message) { + if (m_ShuttingDown) { + return; + } + Ptr keepAlive (this); m_IoStrand.post([this, keepAlive, message]() { SendMessageInternal(message); }); @@ -170,9 +174,17 @@ void JsonRpcConnection::SendMessage(const Dictionary::Ptr& message) void JsonRpcConnection::SendRawMessage(const String& message) { + if (m_ShuttingDown) { + return; + } + Ptr keepAlive (this); m_IoStrand.post([this, keepAlive, message]() { + if (m_ShuttingDown) { + return; + } + m_OutgoingMessagesQueue.emplace_back(message); m_OutgoingMessagesQueued.Set(); }); @@ -180,6 +192,10 @@ void JsonRpcConnection::SendRawMessage(const String& message) void JsonRpcConnection::SendMessageInternal(const Dictionary::Ptr& message) { + if (m_ShuttingDown) { + return; + } + m_OutgoingMessagesQueue.emplace_back(JsonEncode(message)); m_OutgoingMessagesQueued.Set(); } From f074e24d2a1493132313a48b672155b0b9401df8 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 9 Feb 2024 14:44:52 +0100 Subject: [PATCH 042/415] ApiListener#ReplayLog(): stop reading files ASAP on send error --- lib/remote/apilistener.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index ab4a67b69..a05a5735e 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -1462,7 +1462,9 @@ void ApiListener::ReplayLog(const JsonRpcConnection::Ptr& client) return; } - for (;;) { + bool stopReplay = false; + + do { std::unique_lock lock(m_LogLock); CloseLogFile(); @@ -1545,6 +1547,7 @@ void ApiListener::ReplayLog(const JsonRpcConnection::Ptr& client) Log(LogDebug, "ApiListener") << "Error while replaying log for endpoint '" << endpoint->GetName() << "': " << DiagnosticInformation(ex); + stopReplay = true; break; } @@ -1566,6 +1569,10 @@ void ApiListener::ReplayLog(const JsonRpcConnection::Ptr& client) } logStream->Close(); + + if (stopReplay) { + break; + } } if (count > 0) { @@ -1578,16 +1585,14 @@ void ApiListener::ReplayLog(const JsonRpcConnection::Ptr& client) } if (last_sync) { - { - ObjectLock olock2(endpoint); - endpoint->SetSyncing(false); - } - OpenLogFile(); break; } - } + } while (!stopReplay); + + ObjectLock olock2 (endpoint); + endpoint->SetSyncing(false); } void ApiListener::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr& perfdata) From 73db30c08b464999bea20d01da8b0542365b2722 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 13 Feb 2024 09:30:26 +0100 Subject: [PATCH 043/415] Use `Defer` class for cleanup in `ApiListener::ReplayLog()` --- lib/remote/apilistener.cpp | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index a05a5735e..94e3bd122 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -1437,10 +1437,12 @@ void ApiListener::LogGlobHandler(std::vector& files, const String& file) void ApiListener::ReplayLog(const JsonRpcConnection::Ptr& client) { Endpoint::Ptr endpoint = client->GetEndpoint(); + Defer resetEndpointSyncing ([&endpoint]() { + ObjectLock olock(endpoint); + endpoint->SetSyncing(false); + }); if (endpoint->GetLogDuration() == 0) { - ObjectLock olock2(endpoint); - endpoint->SetSyncing(false); return; } @@ -1457,14 +1459,10 @@ void ApiListener::ReplayLog(const JsonRpcConnection::Ptr& client) Zone::Ptr target_zone = target_endpoint->GetZone(); if (!target_zone) { - ObjectLock olock2(endpoint); - endpoint->SetSyncing(false); return; } - bool stopReplay = false; - - do { + for (;;) { std::unique_lock lock(m_LogLock); CloseLogFile(); @@ -1546,9 +1544,7 @@ void ApiListener::ReplayLog(const JsonRpcConnection::Ptr& client) Log(LogDebug, "ApiListener") << "Error while replaying log for endpoint '" << endpoint->GetName() << "': " << DiagnosticInformation(ex); - - stopReplay = true; - break; + return; } peer_ts = pmessage->Get("timestamp"); @@ -1569,10 +1565,6 @@ void ApiListener::ReplayLog(const JsonRpcConnection::Ptr& client) } logStream->Close(); - - if (stopReplay) { - break; - } } if (count > 0) { @@ -1587,12 +1579,9 @@ void ApiListener::ReplayLog(const JsonRpcConnection::Ptr& client) if (last_sync) { OpenLogFile(); - break; + return; } - } while (!stopReplay); - - ObjectLock olock2 (endpoint); - endpoint->SetSyncing(false); + } } void ApiListener::StatsFunc(const Dictionary::Ptr& status, const Array::Ptr& perfdata) From a5a83e311a39499b12985d6e363f6ba9e14a878c Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 13 Feb 2024 09:31:40 +0100 Subject: [PATCH 044/415] Defer: Allow empty initialization & add `SetFunc()` method --- lib/base/defer.hpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/base/defer.hpp b/lib/base/defer.hpp index 2a232619a..2625e02ff 100644 --- a/lib/base/defer.hpp +++ b/lib/base/defer.hpp @@ -22,6 +22,8 @@ public: { } + Defer() = default; + Defer(const Defer&) = delete; Defer(Defer&&) = delete; Defer& operator=(const Defer&) = delete; @@ -39,6 +41,11 @@ public: } } + inline void SetFunc(std::function func) + { + m_Func = std::move(func); + } + inline void Cancel() { From 9222a63ff72d9ac6622fca862fefdf5f5f3c594c Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Tue, 13 Feb 2024 09:33:47 +0100 Subject: [PATCH 045/415] Make sure log file is reopened when `ApiListener::ReplayLog()` returns --- lib/remote/apilistener.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 94e3bd122..c9908446e 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -1466,12 +1466,14 @@ void ApiListener::ReplayLog(const JsonRpcConnection::Ptr& client) std::unique_lock lock(m_LogLock); CloseLogFile(); + Defer reopenLog; if (count == -1 || count > 50000) { OpenLogFile(); lock.unlock(); } else { last_sync = true; + reopenLog.SetFunc([this]() { OpenLogFile(); }); } count = 0; @@ -1577,8 +1579,6 @@ void ApiListener::ReplayLog(const JsonRpcConnection::Ptr& client) } if (last_sync) { - OpenLogFile(); - return; } } From 932a53449dac213730f85f1c4775788e9b7095c2 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 21 Feb 2024 12:04:40 +0100 Subject: [PATCH 046/415] JsonRpcConnection: Raise an exception when trying to send to disconnected clients --- lib/remote/jsonrpcconnection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/remote/jsonrpcconnection.cpp b/lib/remote/jsonrpcconnection.cpp index 90aff0da6..44f1662dd 100644 --- a/lib/remote/jsonrpcconnection.cpp +++ b/lib/remote/jsonrpcconnection.cpp @@ -164,7 +164,7 @@ ConnectionRole JsonRpcConnection::GetRole() const void JsonRpcConnection::SendMessage(const Dictionary::Ptr& message) { if (m_ShuttingDown) { - return; + BOOST_THROW_EXCEPTION(std::runtime_error("Cannot send message to already disconnected API client '" + GetIdentity() + "'!")); } Ptr keepAlive (this); @@ -175,7 +175,7 @@ void JsonRpcConnection::SendMessage(const Dictionary::Ptr& message) void JsonRpcConnection::SendRawMessage(const String& message) { if (m_ShuttingDown) { - return; + BOOST_THROW_EXCEPTION(std::runtime_error("Cannot send message to already disconnected API client '" + GetIdentity() + "'!")); } Ptr keepAlive (this); From e062ceb90169fefa8fb9da7d036744db5ca721d7 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 21 Feb 2024 12:06:11 +0100 Subject: [PATCH 047/415] ApiListener: Catch & supress clients runtime errors --- lib/remote/apilistener.cpp | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index c9908446e..21f6a8d2f 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -1023,7 +1023,12 @@ void ApiListener::ApiTimerHandler() for (const JsonRpcConnection::Ptr& client : endpoint->GetClients()) { if (client->GetTimestamp() == maxTs) { - client->SendMessage(lmessage); + try { + client->SendMessage(lmessage); + } catch (const std::runtime_error& ex) { + Log(LogNotice, "ApiListener") + << "Error while setting log position for identity '" << endpoint->GetName() << "': " << DiagnosticInformation(ex, false); + } } else { client->Disconnect(); } @@ -1195,7 +1200,12 @@ void ApiListener::SyncSendMessage(const Endpoint::Ptr& endpoint, const Dictionar if (client->GetTimestamp() != maxTs) continue; - client->SendMessage(message); + try { + client->SendMessage(message); + } catch (const std::runtime_error& ex) { + Log(LogNotice, "ApiListener") + << "Error while sending message to endpoint '" << endpoint->GetName() << "': " << DiagnosticInformation(ex, false); + } } } } From 9f84c1516e07afe2a99c3418a3d5e3f0802e12f2 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 21 Feb 2024 12:18:35 +0100 Subject: [PATCH 048/415] ApiListener: Reorder logging in `ApiTimerHandler()` --- lib/remote/apilistener.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 21f6a8d2f..7884131c4 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -1021,6 +1021,10 @@ void ApiListener::ApiTimerHandler() maxTs = client->GetTimestamp(); } + Log(LogNotice, "ApiListener") + << "Setting log position for identity '" << endpoint->GetName() << "': " + << Utility::FormatDateTime("%Y/%m/%d %H:%M:%S", ts); + for (const JsonRpcConnection::Ptr& client : endpoint->GetClients()) { if (client->GetTimestamp() == maxTs) { try { @@ -1033,10 +1037,6 @@ void ApiListener::ApiTimerHandler() client->Disconnect(); } } - - Log(LogNotice, "ApiListener") - << "Setting log position for identity '" << endpoint->GetName() << "': " - << Utility::FormatDateTime("%Y/%m/%d %H:%M:%S", ts); } } From ba200f74e199f361ce6a1c6a6741454687f4eb24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lorenz=20K=C3=A4stle?= <12514511+RincewindsHat@users.noreply.github.com> Date: Fri, 30 Aug 2024 12:25:27 +0200 Subject: [PATCH 049/415] Add check_curl to ITL (#9205) * Add check_curl to ITL * small fixes and boolean defaults * Add documentation for check_curl * Replace dash with underscore in variables * Add link to documentation * Change order of argument attributes to adhere to style guide * Shorten description of tls option in itl * Just remove information for check_curl options * itl - check_curl: document -4 and -6 * itl - check_curl: Add haproxy option for check_curl * itl - check_curl: add cookie-jar option * itl - check_curl: add continue_after_certificate option * itl - check_curl: replace dashes with underscores in macros * Update itl/command-plugins.conf Co-authored-by: alvar <8402811+oxzi@users.noreply.github.com> * Update itl/command-plugins.conf Co-authored-by: alvar <8402811+oxzi@users.noreply.github.com> * itl - check_curl: add missing option documentation and reorder options * itl - check_curl: Split certificate lifetime in two parameters * itl - check_curl: replace remaining instances of single parameter for remaining valid time * check_curl: allow assignements for host without address set * check_curl: fix typo expext -> expect * itl - check_curl: add state-regex option and documentation * Add Tls options with version and without * itl - check_curl: fix indentation * itl - check_curl: Set v4/v6 variables * itl - check_curl: Edit description for --sni * doc - check_curl: fix singular-plural typo for curl_max_redir(s) * doc/check_curl: sni description * itl - check_curl: remove superfluous brace * itl - check_curl: add extra-opts parameter --------- Co-authored-by: alvar <8402811+oxzi@users.noreply.github.com> --- doc/10-icinga-template-library.md | 61 +++++++++ itl/command-plugins.conf | 208 +++++++++++++++++++++++++++++- 2 files changed, 268 insertions(+), 1 deletion(-) diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md index f7b57b831..5b2a2fd27 100644 --- a/doc/10-icinga-template-library.md +++ b/doc/10-icinga-template-library.md @@ -738,6 +738,67 @@ http_verbose | **Optional.** Show details for command-line d http_extra_opts | **Optional.** Read extra plugin options from an ini file. http_verify_host | **Optional.** Verify SSL certificate is for the -H hostname (with --sni and -S). Defaults to false. **Only supported by the Nagios plugins version of check\_http, not by the monitoring plugins one.** +### curl + +The [check_curl](https://www.monitoring-plugins.org/doc/man/check_curl.html) plugin +tests the HTTP service on the specified host. It can test normal (http) and secure +(https) servers, follow redirects, search for strings and regular expressions, +check connection times, and report on certificate expiration times. + +The plugin can either test the HTTP response of a server, or if `curl_certificate_valid_days_min_warning`/`curl_certificate_valid_days_min_critical` is set to a non-empty value, the TLS certificate age for a HTTPS host. + +Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): + +Name | Description +---------------------------------|--------------------------------- +curl_extra_opts | **Optional.** Read options from an ini file. +curl_vhost | **Optional.** The virtual host that should be sent in the "Host" header. +curl_ip | **Optional.** The host's address. Defaults to "$address$" if the host's `address` attribute is set, "$address6$" otherwise. +curl_port | **Optional.** The TCP port. Defaults to 80 when not using SSL, 443 otherwise. +curl_ipv4 | **Optional.** Use IPv4 connection. Defaults to false. +curl_ipv6 | **Optional.** Use IPv6 connection. Defaults to false. +curl_tls | **Optional.** Whether to use SSL. Defaults to false. +curl_tls_version | **Optional.** Connect via SSL. Port defaults to 443. VERSION is optional, and prevents auto-negotiation (2 = SSLv2, 3 = SSLv3, 1 = TLSv1, 1.1 = TLSv1.1, 1.2 = TLSv1.2, 1.3 = TLSv1.3). With a '+' suffix, newer versions are also accepted. Note: SSLv2 and SSLv3 are deprecated and are usually disabled in libcurl. +curl_sni | **Optional.** Whether to use SNI. This is the default of `check_curl` in *most* cases and this option will not change this behaviour then. For obscure and old setup it might be necessary to manually activate it. The variable itself defaults to false. +curl_certificate_valid_days_min_warning | **Optional.** Minimum number of days a certificate has to be valid. Port defaults to 443. When this option is used, the URL is not checked (by default). This defines the warning threshold (in days). +curl_certificate_valid_days_min_critical | **Optional.** Minimum number of days a certificate has to be valid. This parameter defines the critical threshold (in days). See also `curl_certificate_valid_days_min_warning` above for more information. +curl_continue_after_certificate | **Optional.** Allows the HTTP check to continue after performing the certificate check. Does nothing unless tls certificate check mode is used (`curl_certificate_valid_days_min_warning`/`curl_certificate_valid_days_min_critical`). (available since Monitoring Plugins v2.3.2) +curl_client_certificate_file | **Optional.** Name of file contains the client certificate (PEM format). +curl_client_certificate_key_file | **Optional.** Name of file contains the private key (PEM format). +curl_ca_cert_file | **Optional.** CA certificate file to verify peer against. +curl_verify_peer_cert | **Optional.** Verify that the peers certificate matches against the hostname +curl_expect_string | **Optional.** Comma-delimited list of strings, at least one of them is expected in the first (status) line of the server response. Default: HTTP/1. +curl_expect_header_string | **Optional.** String to expect in the response headers. +curl_expect_content_string | **Optional.** String to expect in the content. +curl_url | **Optional.** The request URL for GET or POST. Defaults to `/`. +curl_post_data | **Optional.** URL encoded curl POST data. +curl_http_method | **Optional.** Set curl method (for example: HEAD, OPTIONS, TRACE, PUT, DELETE). +curl_no_body | **Optional.** Don't wait for document body: stop reading after headers. (Note that this stilldoes an HTTP GET or POST, not a HEAD.). +curl_max_age | **Optional.** Warn if document is more than seconds old. +curl_content_type | **Optional.** Specify Content-Type header when POSTing. +curl_linespan | **Optional.** Allow regex to span newline. +curl_ereg | **Optional.** A regular expression which the body must match against. Incompatible with curl_no-body. +curl_eregi | **Optional.** A case-insensitive expression which the body must match against. Incompatible with curl_no-body. +curl_invert_regex | **Optional.** Changes behavior of curl_ereg and curl_eregi to return CRITICAL if found, OK if not. +curl_state_regex | **Optional.** Return STATE if regex is found, OK if not. STATE can be one of "critical","warning" +curl_authorization | **Optional.** Add 'username:password' authorization pair. +curl_proxy_authorization | **Optional.** Add 'username:password' authorization pair for proxy. +curl_user_agent | **Optional.** String to be sent in curl header as User Agent. +curl_header | **Optional.** Any other tags to be sent in curl header. Can be an array if multiple headers should be passed to `check_curl`. +curl_extended_perfdata | **Optional.** Print additional perfdata. Defaults to false. +curl_show_body | **Optional.** Print body content below status line +curl_link | **Optional.** Wrap output in HTML link. Defaults to false. +curl_onredirect | **Optional.** How to handle redirect pages. Possible values: "ok" (default), "warning", "critical", "follow", "sticky" (like follow but stick to address), "stickyport" (like sticky but also to port) +curl_max_redirs | **Optional.** Maximum number of redirects +curl_pagesize | **Optional.** Minimum page size required:Maximum page size required. +curl_http_version | **Optional.** Connect via specific HTTP protocol. 1.0 = HTTP/1.0, 1.1 = HTTP/1.1, 2.0 = HTTP/2 (HTTP/2 will fail without -S) +curl_enable_automatic_decompression | **Optional.** Enable automatic decompression of body (CURLOPT_ACCEPT_ENCODING). +curl_haproxy_protocol | **Optional.** Send HAProxy proxy protocol v1 header (CURLOPT_HAPROXYPROTOCOL) (available since Monitoring Plugins v2.4.0) +curl_cookie_jar_file | **Optional.** Path to a cookie jar file. Store cookies in the cookie jar and send them out when requested. (available since Monitoring Plugins v2.3.4) +curl_warning | **Optional.** The warning threshold. +curl_critical | **Optional.** The critical threshold. +curl_timeout | **Optional.** Seconds before connection times out. + ### icmp diff --git a/itl/command-plugins.conf b/itl/command-plugins.conf index e80d1366d..716dde757 100644 --- a/itl/command-plugins.conf +++ b/itl/command-plugins.conf @@ -424,7 +424,7 @@ object CheckCommand "http" { } "--sni" = { set_if = "$http_sni$" - description = "Enable SSL/TLS hostname extension support (SNI)" + description = "Enable SSL/TLS hostname extension support (SNI). This is (normally) the default in modern setups" } "-C" = { value = "$http_certificate$" @@ -566,6 +566,212 @@ object CheckCommand "http" { vars.http_verbose = false } +object CheckCommand "curl" { + import "ipv4-or-ipv6" + + command = [ PluginDir + "/check_curl" ] + + arguments += { + "--extra-opts" = { + value = "$curl_extra_opts$" + description = "Read options from an ini file" + } + "-H" = { + value = "$curl_vhost$" + description = "Host name argument for servers using host headers (virtual host). Append a port to include it in the header (eg: example.com:5000)" + } + "-I" = { + value = "$curl_ip$" + set_if = {{ string(macro("$curl_ip$")) != "" }} + description = "IP address or name (use numeric address if possible to bypass DNS lookup)." + } + "-p" = { + value = "$curl_port$" + description = "Port number (default: 80)" + } + "-4" = { + set_if = "$curl_ipv4$" + description = "Force `check_curl` to use IPv4 instead of choosing automatically" + } + "-6" = { + set_if = "$curl_ipv6$" + description = "Force `check_curl` to use IPv6 instead of choosing automatically" + } + "(-S w/ value)" = { + set_if = {{ macro("$curl_tls$") && string(macro("$curl_tls_version$")) != "" }} + key = "-S" + value = "$curl_tls_version$" + description = "Connect via SSL. Port defaults to 443. VERSION is optional, and prevents auto-negotiation" + } + "(-S w/o value)" = { + set_if = {{ macro("$curl_tls$") && string(macro("$curl_tls_version$")) == "" }} + key = "-S" + description = "Connect via SSL. Port defaults to 443. VERSION is optional, and prevents auto-negotiation" + } + "--sni" = { + set_if = "$curl_sni$" + description = "Enable SSL/TLS hostname extension support (SNI). Default if TLS version > 1.0" + } + "-C" = { + value = "$curl_certificate_valid_days_min_warning$,$curl_certificate_valid_days_min_critical$" + description = "Minimum number of days a certificate has to be valid." + } + "--continue-after-certificate" = { + value = "$curl_continue_after_certificate$" + description = "Allows the HTTP check to continue after performing the certificate check. Does nothing unless -C is used." + } + "-J" = { + value = "$curl_client_certificate_file$" + description = "Name of file that contains the client certificate (PEM format) to be used in establishing the SSL session" + } + "-K" = { + value = "$curl_client_certificate_key_file$" + description = "Name of file containing the private key (PEM format) matching the client certificate" + } + "--ca-cert" = { + value = "$curl_ca_cert_file$" + description = "CA certificate file to verify peer against" + } + "-D" = { + set_if = "$curl_verify_peer_cert$" + description = "Verify the peer's SSL certificate and hostname" + } + "-e" = { + value = "$curl_expect_string$" + description = "Comma-delimited list of strings, at least one of them is expected in the first (status) line of the server response (default: HTTP/), If specified skips all other status line logic (ex: 3xx, 4xx, 5xx processing)" + } + "-d" = { + value = "$curl_expect_header_string$" + description = "String to expect in the response headers" + } + "-s" = { + value = "$curl_expect_content_string$" + description = "String to expect in the content" + } + "-u" = { + value = "$curl_url$" + description = "URL to GET or POST (default: /)" + } + "-P" = { + value = "$curl_post_data$" + description = "URL encoded http POST data" + } + "-j" = { + value = "$curl_http_method$" + description = "Set HTTP method (for example: HEAD, OPTIONS, TRACE, PUT, DELETE, CONNECT)" + } + "-N" = { + value = "$curl_no_body$" + description = "Don't wait for document body: stop reading after headers. (Note that this still does an HTTP GET or POST, not a HEAD.)" + } + "-M" = { + value = "$curl_max_age$" + description = "Warn if document is more than SECONDS old. the number can also be of the form '10m' for minutes, '10h' for hours, or '10d' for days." + } + "-T" = { + value = "$curl_content_type$" + description = "specify Content-Type header media type when POSTing" + } + "-l" = { + value = "$curl_linespan$" + description = "Allow regex to span newlines (must precede -r or -R)" + } + "-r" = { + value = "$curl_ereg$" + description = "Search page for regex STRING" + } + "-R" = { + value = "$curl_eregi$" + description = "Search page for case-insensitive regex STRING" + } + "--invert-regex" = { + set_if = "$curl_invert_regex$" + description = "When using regex, return CRITICAL if found, OK if not" + } + "--state-regex" = { + value = "$curl_state_regex$" + description = "Return STATE if regex is found, OK if not" + } + "-a" = { + value = "$curl_authorization$" + description = "Username:password on sites with basic authentication" + } + "-b" = { + value = "$curl_proxy_authorization$" + description = "Username:password on proxy-servers with basic authentication" + } + "-A" = { + value = "$curl_user_agent$" + description = "String to be sent in http header as 'User Agent'" + } + "-k" = { + value = "$curl_header$" + repeat_key = true + description = "Any other tags to be sent in http header. Use multiple times for additional headers" + } + "-E" = { + set_if = "$curl_extended_perfdata$" + description = "Print additional performance data" + } + "-B" = { + set_if = "$curl_show_body$" + description = "Print body content below status line" + } + "-L" = { + set_if = "$curl_link$" + description = "Wrap output in HTML link (obsoleted by urlize)" + } + "-f" = { + value = "$curl_onredirect$" + description = "Options: How to handle redirected pages." + } + "--max-redirs" = { + value = "$curl_max_redirs$" + description = "Maximal number of redirects (default: 15)" + } + "-m" = { + value = "$curl_pagesize$" + description = "Minimum page size required (bytes) : Maximum page size required (bytes)" + } + "--http-version" = { + value = "$curl_http_version$" + description = "Connect via specific HTTP protocol. 1.0 = HTTP/1.0, 1.1 = HTTP/1.1, 2.0 = HTTP/2 (HTTP/2 will fail without -S)" + } + "--enable-automatic-decompression" = { + set_if = "$curl_enable_automatic_decompression$" + description = "Enable automatic decompression of body (CURLOPT_ACCEPT_ENCODING)." + } + "--haproxy-protocol" = { + set_if = "$curl_haproxy_protocol$" + description = "Send HAProxy proxy protocol v1 header (CURLOPT_HAPROXYPROTOCOL)" + } + "--cookie-jar" = { + value = "$curl_cookie_jar_file$" + description = "Store cookies in the cookie jar file and send them out when requested." + } + "-w" = { + value = "$curl_warning$" + description = "Response time to result in warning status (seconds)" + } + "-c" = { + value = "$curl_critical$" + description = "Response time to result in critical status (seconds)" + } + "-t" = { + value = "$curl_timeout$" + description = "Seconds before connection times out (default: 10)" + } + } + + vars.curl_ip = "$check_address$" + vars.curl_link = false + vars.curl_invert_regex = false + vars.curl_show_body = false + vars.curl_extended_perfdata = false + vars.check_ipv4 = "$curl_ipv4$" + vars.check_ipv6 = "$curl_ipv6$" +} + object CheckCommand "ftp" { import "ipv4-or-ipv6" From 79e3cb2a95430739f12e1014158d1e413ad62841 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Wed, 4 Sep 2024 10:20:41 +0200 Subject: [PATCH 050/415] Utility::ReleaseHelper(): remove detection of EOL distros We only support /etc/os-release owners. --- lib/base/utility.cpp | 68 +------------------------------------------- 1 file changed, 1 insertion(+), 67 deletions(-) diff --git a/lib/base/utility.cpp b/lib/base/utility.cpp index 338e4f77f..a2e461e5c 100644 --- a/lib/base/utility.cpp +++ b/lib/base/utility.cpp @@ -1649,37 +1649,8 @@ static bool ReleaseHelper(String *platformName, String *platformVersion) return true; } - /* You are using a distribution which supports LSB. */ - FILE *fp = popen("type lsb_release >/dev/null 2>&1 && lsb_release -s -i 2>&1", "r"); - - if (fp) { - std::ostringstream msgbuf; - char line[1024]; - while (fgets(line, sizeof(line), fp)) - msgbuf << line; - int status = pclose(fp); - if (WEXITSTATUS(status) == 0) { - if (platformName) - *platformName = msgbuf.str(); - } - } - - fp = popen("type lsb_release >/dev/null 2>&1 && lsb_release -s -r 2>&1", "r"); - - if (fp) { - std::ostringstream msgbuf; - char line[1024]; - while (fgets(line, sizeof(line), fp)) - msgbuf << line; - int status = pclose(fp); - if (WEXITSTATUS(status) == 0) { - if (platformVersion) - *platformVersion = msgbuf.str(); - } - } - /* OS X */ - fp = popen("type sw_vers >/dev/null 2>&1 && sw_vers -productName 2>&1", "r"); + FILE* fp = popen("type sw_vers >/dev/null 2>&1 && sw_vers -productName 2>&1", "r"); if (fp) { std::ostringstream msgbuf; @@ -1715,43 +1686,6 @@ static bool ReleaseHelper(String *platformName, String *platformVersion) } } - /* Centos/RHEL < 7 */ - release.close(); - release.open("/etc/redhat-release"); - if (release.is_open()) { - std::string release_line; - getline(release, release_line); - - String info = release_line; - - /* example: Red Hat Enterprise Linux Server release 6.7 (Santiago) */ - if (platformName) - *platformName = info.SubStr(0, info.Find("release") - 1); - - if (platformVersion) - *platformVersion = info.SubStr(info.Find("release") + 8); - - return true; - } - - /* sles 11 sp3, opensuse w/e */ - release.close(); - release.open("/etc/SuSE-release"); - if (release.is_open()) { - std::string release_line; - getline(release, release_line); - - String info = release_line; - - if (platformName) - *platformName = info.SubStr(0, info.FindFirstOf(" ")); - - if (platformVersion) - *platformVersion = info.SubStr(info.FindFirstOf(" ") + 1); - - return true; - } - /* Just give up */ return false; #endif /* _WIN32 */ From c9159494c0709dcfd4c1dc233894efa8cdf820ed Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 28 Aug 2024 17:34:17 +0200 Subject: [PATCH 051/415] HttpServerConnection: Drop yet another superfluous `CpuBoundWork` usage --- lib/remote/httpserverconnection.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/remote/httpserverconnection.cpp b/lib/remote/httpserverconnection.cpp index dc6492a18..b9a8ab814 100644 --- a/lib/remote/httpserverconnection.cpp +++ b/lib/remote/httpserverconnection.cpp @@ -348,8 +348,6 @@ bool EnsureValidBody( Array::Ptr permissions = authenticatedUser->GetPermissions(); if (permissions) { - CpuBoundWork evalPermissions (yc); - ObjectLock olock(permissions); for (const Value& permissionInfo : permissions) { From 74009f0fcb6395e13bd41fd00e46aa7f6e8941c2 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 30 Aug 2024 18:00:00 +0200 Subject: [PATCH 052/415] Don't use thread-local variable in coroutine & process final `cr` in global thread pool --- lib/methods/ifwapichecktask.cpp | 218 ++++++++++++-------------------- 1 file changed, 83 insertions(+), 135 deletions(-) diff --git a/lib/methods/ifwapichecktask.cpp b/lib/methods/ifwapichecktask.cpp index 8516d70c0..16e2f7e63 100644 --- a/lib/methods/ifwapichecktask.cpp +++ b/lib/methods/ifwapichecktask.cpp @@ -33,55 +33,6 @@ using namespace icinga; REGISTER_FUNCTION_NONCONST(Internal, IfwApiCheck, &IfwApiCheckTask::ScriptFunc, "checkable:cr:resolvedMacros:useResolvedMacros"); -static void ReportIfwCheckResult( - const Checkable::Ptr& checkable, const Value& cmdLine, const CheckResult::Ptr& cr, - const String& output, double start, double end, int exitcode = 3, const Array::Ptr& perfdata = nullptr -) -{ - if (Checkable::ExecuteCommandProcessFinishedHandler) { - ProcessResult pr; - pr.PID = -1; - pr.Output = perfdata ? output + " |" + String(perfdata->Join(" ")) : output; - pr.ExecutionStart = start; - pr.ExecutionEnd = end; - pr.ExitStatus = exitcode; - - Checkable::ExecuteCommandProcessFinishedHandler(cmdLine, pr); - } else { - auto splittedPerfdata (perfdata); - - if (perfdata) { - splittedPerfdata = new Array(); - ObjectLock oLock (perfdata); - - for (String pv : perfdata) { - PluginUtility::SplitPerfdata(pv)->CopyTo(splittedPerfdata); - } - } - - cr->SetOutput(output); - cr->SetPerformanceData(splittedPerfdata); - cr->SetState((ServiceState)exitcode); - cr->SetExitStatus(exitcode); - cr->SetExecutionStart(start); - cr->SetExecutionEnd(end); - cr->SetCommand(cmdLine); - - checkable->ProcessCheckResult(cr); - } -} - -static void ReportIfwCheckResult( - boost::asio::yield_context yc, const Checkable::Ptr& checkable, const Value& cmdLine, - const CheckResult::Ptr& cr, const String& output, double start -) -{ - double end = Utility::GetTime(); - CpuBoundWork cbw (yc); - - ReportIfwCheckResult(checkable, cmdLine, cr, output, start, end); -} - static const char* GetUnderstandableError(const std::exception& ex) { auto se (dynamic_cast(&ex)); @@ -93,10 +44,12 @@ static const char* GetUnderstandableError(const std::exception& ex) return ex.what(); } +// Note: If DoIfwNetIo returns due to an error, the plugin output of the specified CheckResult (cr) will always be set, +// and if it was successful, the cr exit status, plugin state and performance data (if any) will also be overridden. +// Therefore, you have to take care yourself of setting all the other necessary fields for the check result. static void DoIfwNetIo( - boost::asio::yield_context yc, const Checkable::Ptr& checkable, const Array::Ptr& cmdLine, - const CheckResult::Ptr& cr, const String& psCommand, const String& psHost, const String& san, const String& psPort, - AsioTlsStream& conn, boost::beast::http::request& req, double start + boost::asio::yield_context yc, const CheckResult::Ptr& cr, const String& psCommand, const String& psHost, const String& san, + const String& psPort, AsioTlsStream& conn, boost::beast::http::request& req ) { namespace http = boost::beast::http; @@ -107,11 +60,7 @@ static void DoIfwNetIo( try { Connect(conn.lowest_layer(), psHost, psPort, yc); } catch (const std::exception& ex) { - ReportIfwCheckResult( - yc, checkable, cmdLine, cr, - "Can't connect to IfW API on host '" + psHost + "' port '" + psPort + "': " + GetUnderstandableError(ex), - start - ); + cr->SetOutput("Can't connect to IfW API on host '" + psHost + "' port '" + psPort + "': " + GetUnderstandableError(ex)); return; } @@ -120,12 +69,7 @@ static void DoIfwNetIo( try { sslConn.async_handshake(conn.next_layer().client, yc); } catch (const std::exception& ex) { - ReportIfwCheckResult( - yc, checkable, cmdLine, cr, - "TLS handshake with IfW API on host '" + psHost + "' (SNI: '" + san - + "') port '" + psPort + "' failed: " + GetUnderstandableError(ex), - start - ); + cr->SetOutput("TLS handshake with IfW API on host '" + psHost + "' (SNI: '" + san+ "') port '" + psPort + "' failed: " + GetUnderstandableError(ex)); return; } @@ -135,15 +79,10 @@ static void DoIfwNetIo( try { cn = GetCertificateCN(cert); - } catch (const std::exception&) { - } + } catch (const std::exception&) { } - ReportIfwCheckResult( - yc, checkable, cmdLine, cr, - "Certificate validation failed for IfW API on host '" + psHost + "' (SNI: '" + san + "'; CN: " - + (cn.IsString() ? "'" + cn + "'" : "N/A") + ") port '" + psPort + "': " + sslConn.GetVerifyError(), - start - ); + cr->SetOutput("Certificate validation failed for IfW API on host '" + psHost + "' (SNI: '" + san + "'; CN: " + + (cn.IsString() ? "'" + cn + "'" : "N/A") + ") port '" + psPort + "': " + sslConn.GetVerifyError()); return; } @@ -151,87 +90,56 @@ static void DoIfwNetIo( http::async_write(conn, req, yc); conn.async_flush(yc); } catch (const std::exception& ex) { - ReportIfwCheckResult( - yc, checkable, cmdLine, cr, - "Can't send HTTP request to IfW API on host '" + psHost + "' port '" + psPort + "': " + GetUnderstandableError(ex), - start - ); + cr->SetOutput("Can't send HTTP request to IfW API on host '" + psHost + "' port '" + psPort + "': " + GetUnderstandableError(ex)); return; } try { http::async_read(conn, buf, resp, yc); } catch (const std::exception& ex) { - ReportIfwCheckResult( - yc, checkable, cmdLine, cr, - "Can't read HTTP response from IfW API on host '" + psHost + "' port '" + psPort + "': " + GetUnderstandableError(ex), - start - ); + cr->SetOutput("Can't read HTTP response from IfW API on host '" + psHost + "' port '" + psPort + "': " + GetUnderstandableError(ex)); return; } - double end = Utility::GetTime(); - { boost::system::error_code ec; sslConn.async_shutdown(yc[ec]); } - CpuBoundWork cbw (yc); Value jsonRoot; try { jsonRoot = JsonDecode(resp.body()); } catch (const std::exception& ex) { - ReportIfwCheckResult( - checkable, cmdLine, cr, - "Got bad JSON from IfW API on host '" + psHost + "' port '" + psPort + "': " + ex.what(), start, end - ); + cr->SetOutput("Got bad JSON from IfW API on host '" + psHost + "' port '" + psPort + "': " + ex.what()); return; } if (!jsonRoot.IsObjectType()) { - ReportIfwCheckResult( - checkable, cmdLine, cr, - "Got JSON, but not an object, from IfW API on host '" - + psHost + "' port '" + psPort + "': " + JsonEncode(jsonRoot), - start, end - ); + cr->SetOutput("Got JSON, but not an object, from IfW API on host '"+ psHost + "' port '" + psPort + "': " + JsonEncode(jsonRoot)); return; } Value jsonBranch; if (!Dictionary::Ptr(jsonRoot)->Get(psCommand, &jsonBranch)) { - ReportIfwCheckResult( - checkable, cmdLine, cr, - "Missing ." + psCommand + " in JSON object from IfW API on host '" - + psHost + "' port '" + psPort + "': " + JsonEncode(jsonRoot), - start, end - ); + cr->SetOutput("Missing ." + psCommand + " in JSON object from IfW API on host '" + psHost + "' port '" + psPort + "': " + JsonEncode(jsonRoot)); return; } if (!jsonBranch.IsObjectType()) { - ReportIfwCheckResult( - checkable, cmdLine, cr, - "." + psCommand + " in JSON from IfW API on host '" - + psHost + "' port '" + psPort + "' is not an object: " + JsonEncode(jsonBranch), - start, end - ); + cr->SetOutput("." + psCommand + " in JSON from IfW API on host '" + psHost + "' port '" + psPort + "' is not an object: " + JsonEncode(jsonBranch)); return; } Dictionary::Ptr result = jsonBranch; - Value exitcode; + Value rawExitcode; - if (!result->Get("exitcode", &exitcode)) { - ReportIfwCheckResult( - checkable, cmdLine, cr, + if (!result->Get("exitcode", &rawExitcode)) { + cr->SetOutput( "Missing ." + psCommand + ".exitcode in JSON object from IfW API on host '" - + psHost + "' port '" + psPort + "': " + JsonEncode(result), - start, end + + psHost + "' port '" + psPort + "': " + JsonEncode(result) ); return; } @@ -239,27 +147,25 @@ static void DoIfwNetIo( static const std::set exitcodes {ServiceOK, ServiceWarning, ServiceCritical, ServiceUnknown}; static const auto exitcodeList (Array::FromSet(exitcodes)->Join(", ")); - if (!exitcode.IsNumber() || exitcodes.find(exitcode) == exitcodes.end()) { - ReportIfwCheckResult( - checkable, cmdLine, cr, - "Got bad exitcode " + JsonEncode(exitcode) + " from IfW API on host '" + psHost + "' port '" + psPort - + "', expected one of: " + exitcodeList, - start, end + if (!rawExitcode.IsNumber() || exitcodes.find(rawExitcode) == exitcodes.end()) { + cr->SetOutput( + "Got bad exitcode " + JsonEncode(rawExitcode) + " from IfW API on host '" + psHost + "' port '" + psPort + + "', expected one of: " + exitcodeList ); return; } + auto exitcode (static_cast(rawExitcode.Get())); + auto perfdataVal (result->Get("perfdata")); Array::Ptr perfdata; try { perfdata = perfdataVal; } catch (const std::exception&) { - ReportIfwCheckResult( - checkable, cmdLine, cr, + cr->SetOutput( "Got bad perfdata " + JsonEncode(perfdataVal) + " from IfW API on host '" - + psHost + "' port '" + psPort + "', expected an array", - start, end + + psHost + "' port '" + psPort + "', expected an array" ); return; } @@ -269,18 +175,20 @@ static void DoIfwNetIo( for (auto& pv : perfdata) { if (!pv.IsString()) { - ReportIfwCheckResult( - checkable, cmdLine, cr, + cr->SetOutput( "Got bad perfdata value " + JsonEncode(perfdata) + " from IfW API on host '" - + psHost + "' port '" + psPort + "', expected an array of strings", - start, end + + psHost + "' port '" + psPort + "', expected an array of strings" ); return; } } + + cr->SetPerformanceData(PluginUtility::SplitPerfdata(perfdata->Join(" "))); } - ReportIfwCheckResult(checkable, cmdLine, cr, result->Get("checkresult"), start, end, exitcode, perfdata); + cr->SetState(exitcode); + cr->SetExitStatus(exitcode); + cr->SetOutput(result->Get("checkresult")); } void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, @@ -344,6 +252,34 @@ void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes String username = resolveMacros("$ifw_api_username$"); String password = resolveMacros("$ifw_api_password$"); + // Use this lambda to process the final Ifw check result. Callers don't need to pass the check result + // as an argument, as the lambda already captures the `cr` and notices all the `cr` changes made across + // the code. You just need to set the necessary cr fields when appropriated and then call this closure. + std::function reportResult; + + if (auto callback = Checkable::ExecuteCommandProcessFinishedHandler; callback) { + reportResult = [cr, callback = std::move(callback)]() { + ProcessResult pr; + pr.PID = -1; + if (auto pd = cr->GetPerformanceData(); pd) { + pr.Output = cr->GetOutput() +" |" + String(pd->Join(" ")); + } else { + pr.Output = cr->GetOutput(); + } + pr.ExecutionStart = cr->GetExecutionStart(); + pr.ExecutionEnd = cr->GetExecutionEnd(); + pr.ExitStatus = cr->GetExitStatus(); + + callback(cr->GetCommand(), pr); + }; + } else { + reportResult = [checkable, cr]() { checkable->ProcessCheckResult(cr); }; + } + + // Set the default check result state and exit code to unknown for the moment! + cr->SetExitStatus(ServiceUnknown); + cr->SetState(ServiceUnknown); + Dictionary::Ptr params = new Dictionary(); if (arguments) { @@ -369,11 +305,12 @@ void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes if (kv.second.GetType() == ValueObject) { auto now (Utility::GetTime()); - ReportIfwCheckResult( - checkable, command->GetName(), cr, - "$ifw_api_arguments$ may not directly contain objects (especially functions).", now, now - ); + cr->SetCommand(command->GetName()); + cr->SetExecutionStart(now); + cr->SetExecutionEnd(now); + cr->SetOutput("$ifw_api_arguments$ may not directly contain objects (especially functions)."); + reportResult(); return; } } @@ -498,12 +435,17 @@ void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes auto& io (IoEngine::Get().GetIoContext()); auto strand (Shared::Make(io)); Shared::Ptr ctx; - double start = Utility::GetTime(); + + cr->SetExecutionStart(Utility::GetTime()); + cr->SetCommand(cmdLine); try { ctx = SetupSslContext(cert, key, ca, crl, DEFAULT_TLS_CIPHERS, DEFAULT_TLS_PROTOCOLMIN, DebugInfo()); } catch (const std::exception& ex) { - ReportIfwCheckResult(checkable, cmdLine, cr, ex.what(), start, Utility::GetTime()); + cr->SetOutput(ex.what()); + cr->SetExecutionEnd(Utility::GetTime()); + + reportResult(); return; } @@ -511,7 +453,7 @@ void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes IoEngine::SpawnCoroutine( *strand, - [strand, checkable, cmdLine, cr, psCommand, psHost, expectedSan, psPort, conn, req, start, checkTimeout](asio::yield_context yc) { + [strand, checkable, cr, psCommand, psHost, expectedSan, psPort, conn, req, checkTimeout, reportResult = std::move(reportResult)](asio::yield_context yc) { Timeout::Ptr timeout = new Timeout(strand->context(), *strand, boost::posix_time::microseconds(int64_t(checkTimeout * 1e6)), [&conn, &checkable](boost::asio::yield_context yc) { Log(LogNotice, "IfwApiCheckTask") @@ -525,7 +467,13 @@ void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes Defer cancelTimeout ([&timeout]() { timeout->Cancel(); }); - DoIfwNetIo(yc, checkable, cmdLine, cr, psCommand, psHost, expectedSan, psPort, *conn, *req, start); + DoIfwNetIo(yc, cr, psCommand, psHost, expectedSan, psPort, *conn, *req); + + cr->SetExecutionEnd(Utility::GetTime()); + + // Post the check result processing to the global pool not to block the I/O threads, + // which could affect processing important RPC messages and HTTP connections. + Utility::QueueAsyncCallback(reportResult); } ); } From e7670e9ba9ef195c9f6787fcbdb1f9d07abd329d Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Thu, 5 Sep 2024 16:40:10 +0200 Subject: [PATCH 053/415] check_systemd: harmonize ITL w/ upstream Harmonize the arguments with the upstream CheckCommand[0], including a patch to use the ITL variables[1]. [0]: https://github.com/Josef-Friedrich/check_systemd/blob/main/contrib/icinga2/command.conf [1]: https://github.com/Josef-Friedrich/check_systemd/pull/38 Co-Authored-By: RincewindsHat <12514511+RincewindsHat@users.noreply.github.com> --- doc/10-icinga-template-library.md | 35 ++++++--- itl/plugins-contrib.d/systemd.conf | 114 ++++++++++++++++++++--------- 2 files changed, 104 insertions(+), 45 deletions(-) diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md index f1abee769..56a9a797f 100644 --- a/doc/10-icinga-template-library.md +++ b/doc/10-icinga-template-library.md @@ -3653,21 +3653,32 @@ iostat\_cwrite | **Required.** Critical threshold for KB/s writes (default: 200) #### systemd The [check_systemd](https://github.com/Josef-Friedrich/check_systemd) plugin -will report a degraded system to your monitoring solution. It requires only the [nagiosplugin](https://nagiosplugin.readthedocs.io/en/stable) library. +will report a degraded system to your monitoring solution. Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description ---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------- -systemd\_unit | **Optional.** Name of the systemd unit that is being tested. -systemd\_exclude\_unit | **Optional.** Exclude a systemd unit from the checks. This option can be applied multiple times. Also supports regular expressions. -systemd\_no\_startup\_time | **Optional.** Don’t check the startup time. Using this option the options `systemd_warning` and `systemd_critical` have no effect. (Default: `false`) -systemd\_warning | **Optional.** Startup time in seconds to result in a warning status. (Default: `60s`) -systemd\_critical | **Optional.** Startup time in seconds to result in a critical status. (Default: `120s`) -systemd\_dead\_timers | **Optional.** Detect dead / inactive timers. (Default: `false`) -systemd\_dead\_timers\_warning | **Optional.** Time ago in seconds for dead / inactive timers to trigger a warning state (by default 6 days). -systemd\_dead\_timers\_critical | **Optional.** Time ago in seconds for dead / inactive timers to trigger a critical state (by default 7 days). -systemd\_verbose\_level | **Optional.** Increase verbosity level (Accepted values: `1`, `2` or `3`). (Defaults to none) +Name | Description +---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------- +systemd\_verbose\_level | **Optional.** Increase verbosity level (Accepted values: `1`, `2` or `3`). (Defaults to none) +systemd\_ignore\_inactive\_state | **Optional.** Ignore an inactive state on a specific unit. Only affective if used with `systemd_unit`. +systemd\_include | **Optional.** Include systemd units to the checks, regular expressions are supported. This option can be applied multiple times. +systemd\_unit | **Optional.** Name of the systemd unit that is being tested. +systemd\_include\_type | **Optional.** Unit types to be tested (for example: `service`, `timer`). This option can be applied multiple times. +systemd\_exclude\_unit | **Optional.** Exclude a systemd unit from the checks, regular expressions are supported. This option can be applied multiple times. +systemd\_exclude\_unit\_name | **Optional.** Exclude a systemd unit from the checks. This option can be applied multiple times. +systemd\_exclude\_type | **Optional.** Exclude a systemd unit type (for example: `service`, `timer`) +systemd\_state | **Optional.** Specify the active state that the systemd unit must have (for example: `active`, `inactive`) +systemd\_dead\_timers | **Optional.** Detect dead / inactive timers, see `systemd_dead_timers_{warning,critical}`. (Default `false`) +systemd\_dead\_timers\_warning | **Optional.** Time ago in seconds for dead / inactive timers to trigger a warning state. (Default 6 days) +systemd\_dead\_timers\_critical | **Optional.** Time ago in seconds for dead / inactive timers to trigger a critical state. (Default 7 days) +systemd\_no\_startup\_time | **Optional.** Don't check the startup time. Using this option, the options `systemd_{warning,critical}` have no effect. (Default `false`) +systemd\_warning | **Optional.** Startup time in seconds to result in a warning status. (Default 60 seconds) +systemd\_critical | **Optional.** Startup time in seconds to result in a critical status. (Default 120 seconds) +systemd\_dbus | **Optional.** Use systemd's D-Bus API instead of parsing command output. Only partially implemented! +systemd\_cli | **Optional.** Use text output from parsing command output. (Default) +systemd\_user | **Optional.** Also show user (systemctl --user) units. + + #### yum diff --git a/itl/plugins-contrib.d/systemd.conf b/itl/plugins-contrib.d/systemd.conf index 236499bc4..2638165ab 100644 --- a/itl/plugins-contrib.d/systemd.conf +++ b/itl/plugins-contrib.d/systemd.conf @@ -4,39 +4,7 @@ object CheckCommand "systemd" { command = [ PluginContribDir + "/check_systemd" ] arguments = { - "--unit" = { - value = "$systemd_unit$" - description = "Name of the systemd unit that is being tested." - } - "--exclude" = { - value = "$systemd_exclude_unit$" - description = "Exclude a systemd unit from the checks. This option can be applied multiple times. Also supports regular expressions." - repeat_key = true - } - "--no-startup-time" = { - set_if = "$systemd_no_startup_time$" - description = "Don’t check the startup time. Using this option the options `systemd_warning` and `systemd_critical` have no effect. (Default: `false`)" - } - "--warning" = { - value = "$systemd_warning$" - description = "Startup time in seconds to result in a warning status. (Default: `60s`)" - } - "--critical" = { - value = "$systemd_critical$" - description = "Startup time in seconds to result in a critical status. (Default: `120s`)" - } - "--dead-timers" = { - set_if = "$systemd_dead_timers$" - description = "Detect dead / inactive timers. (Default: `false`)" - } - "--dead-timers-warning" = { - value = "$systemd_dead_timers_warning$" - description = "Time ago in seconds for dead / inactive timers to trigger a warning state (by default 6 days)." - } - "--dead-timers-critical" = { - value = "$systemd_dead_timers_critical$" - description = "Time ago in seconds for dead / inactive timers to trigger a critical state (by default 7 days)." - } + /* General options */ "-v" = { set_if = {{ macro("$systemd_verbose_level$") == 1 }} description = "Increase verbosity level (Accepted values: `1`, `2` or `3`). Defaults to none." @@ -47,5 +15,85 @@ object CheckCommand "systemd" { "-vvv" = { set_if = {{ macro("$systemd_verbose_level$") == 3 }} } + + /* Options related to unit selection */ + "--ignore-inactive-state" = { + set_if = "$systemd_ignore_inactive_state$" + description = "Ignore an inactive state on a specific unit. Only affective if used with `systemd_unit`." + } + "--include" = { + value = "$systemd_include$" + description = "Include systemd units to the checks, regular expressions are supported. This option can be applied multiple times." + repeat_key = true + } + "--unit" = { + value = "$systemd_unit$" + description = "Name of the systemd unit that is being tested." + } + "--include-type" = { + value = "$systemd_include_type$" + description = "Unit types to be tested (for example: `service`, `timer`). This option can be applied multiple times." + repeat_key = true + } + "--exclude" = { + value = "$systemd_exclude_unit$" + description = "Exclude a systemd unit from the checks, regular expressions are supported. This option can be applied multiple times." + repeat_key = true + } + "--exclude-unit" = { + value = "$systemd_exclude_unit_name$" + description = "Exclude a systemd unit from the checks. This option can be applied multiple times." + repeat_key = true + } + "--exclude-type" = { + value = "$systemd_exclude_type$" + description = "Exclude a systemd unit type (for example: `service`, `timer`)" + } + "--state" = { + value = "$systemd_state$" + description = "Specify the active state that the systemd unit must have (for example: `active`, `inactive`)" + } + + /* Timers related options */ + "--dead-timers" = { + set_if = "$systemd_dead_timers$" + description = "Detect dead / inactive timers, see `systemd_dead_timers_{warning,critical}`. (Default `false`)" + } + "--dead-timers-warning" = { + value = "$systemd_dead_timers_warning$" + description = "Time ago in seconds for dead / inactive timers to trigger a warning state. (Default 6 days)" + } + "--dead-timers-critical" = { + value = "$systemd_dead_timers_critical$" + description = "Time ago in seconds for dead / inactive timers to trigger a critical state. (Default 7 days)" + } + + /* Startup time related options */ + "--no-startup-time" = { + set_if = "$systemd_no_startup_time$" + description = "Don't check the startup time. Using this option, the options `systemd_{warning,critical}` have no effect. (Default `false`)" + } + "--warning" = { + value = "$systemd_warning$" + description = "Startup time in seconds to result in a warning status. (Default 60 seconds)" + } + "--critical" = { + value = "$systemd_critical$" + description = "Startup time in seconds to result in a critical status. (Default 120 seconds)" + } + + /* Monitoring data acquisition */ + "--dbus" = { + set_if = "$systemd_dbus$" + description = "Use systemd's D-Bus API instead of parsing command output. Only partially implemented!" + } + "--cli" = { + set_if = "$systemd_cli$" + description = "Use text output from parsing command output. (Default)" + } + "--user" = { + set_if = "$systemd_user$" + description = "Also show user (systemctl --user) units." + } } } From b8932e67fc0989089703e96fae7ee2626b6656ff Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 10 Sep 2024 11:03:50 +0200 Subject: [PATCH 054/415] tests: Fix test `FormatDateTime` with invalid formats on macOS/*BSD --- test/base-utility.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/test/base-utility.cpp b/test/base-utility.cpp index 8fe1814da..51eba962b 100644 --- a/test/base-utility.cpp +++ b/test/base-utility.cpp @@ -199,7 +199,16 @@ BOOST_AUTO_TEST_CASE(FormatDateTime) { // treat them as an error which our implementation currently maps to the empty string due to strftime() not // properly reporting errors. If this limitation of our implementation is lifted, other behavior like throwing // an exception would also be valid. - BOOST_CHECK_MESSAGE(result.empty() || result == format, + + std::string percentLessOutput(format); + // `strftime(3)` seems to return the provided invalid format specifiers on all Platforms as documented above, + // i.e. even on macOS, but the macOS/*BSD libc implementations of `strftime(3)` appears to behave differently + // and causes the `%` character not to be populated into the results buffer if invalid format specifiers such + // as `"x %! y"` are given. If such specifiers are provided, the output will contain `x ! y` instead of the + // given invalid format specifiers. + percentLessOutput.erase(std::remove(percentLessOutput.begin(), percentLessOutput.end(), '%'), percentLessOutput.end()); + + BOOST_CHECK_MESSAGE(result.empty() || result == format || result == percentLessOutput, "FormatDateTime(" << std::quoted(format) << ", " << ts << ") = " << std::quoted(result) << " should be one of [\"\", " << std::quoted(format) << "]"); } From 26f43b0b48ce0e68485a380ca7d72f870f01b476 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 11 Sep 2024 14:08:27 +0200 Subject: [PATCH 055/415] IcingaDB: Don't sync partially initialised objects --- lib/icingadb/icingadb-objects.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index 948751460..920251969 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -286,6 +286,15 @@ void IcingaDB::UpdateAllConfigObjects() if (lcType != GetLowerCaseTypeNameDB(object)) continue; + // If we encounter not yet activated objects, i.e. they are currently being loaded and are about to + // be activated, but are still partially initialised, we want to exclude them from the config dump + // before we end up in a nullptr deference and crash the Icinga 2 process. Should these excluded + // objects later reach the activation process, they will be captured via the `OnActiveChanged` event + // and processed in IcingaDB::VersionChangedHandler() as runtime updates. + if (!object->IsActive()) { + continue; + } + std::vector runtimeUpdates; CreateConfigUpdate(object, lcType, hMSets, runtimeUpdates, false); From 1f31f725f894ab767b55f10dd3cdc251153b4bff Mon Sep 17 00:00:00 2001 From: Josef Friedrich Date: Thu, 12 Sep 2024 08:39:15 +0200 Subject: [PATCH 056/415] Remove trailing whitespaces in the markdown files of the documentation --- doc/01-about.md | 1 - doc/10-icinga-template-library.md | 2 +- doc/11-cli-commands.md | 2 +- doc/12-icinga2-api.md | 12 ++++++------ doc/14-features.md | 2 +- doc/18-library-reference.md | 6 +++--- doc/19-technical-concepts.md | 8 ++++---- doc/22-selinux.md | 8 ++++---- doc/23-migrating-from-icinga-1x.md | 2 +- doc/24-appendix.md | 1 - 10 files changed, 21 insertions(+), 23 deletions(-) diff --git a/doc/01-about.md b/doc/01-about.md index 4a665534a..73e163bae 100644 --- a/doc/01-about.md +++ b/doc/01-about.md @@ -67,4 +67,3 @@ Read more about development builds in the [development chapter](21-development.m Icinga 2 and the Icinga 2 documentation are licensed under the terms of the GNU General Public License Version 2. You will find a copy of this license in the LICENSE file included in the source package. - diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md index a4b88afee..249e43ff4 100644 --- a/doc/10-icinga-template-library.md +++ b/doc/10-icinga-template-library.md @@ -3512,7 +3512,7 @@ thola_identify_discover_timeouts | **Optional.** The number of discover timeou > **Note**: > -> One of the variables `thola_identify_model`, `thola_identify_os_version`, +> One of the variables `thola_identify_model`, `thola_identify_os_version`, > `thola_identify_vendor` or `thola_identify_serial_number` must be set ##### thola-memory-usage diff --git a/doc/11-cli-commands.md b/doc/11-cli-commands.md index 457656cb0..cd130a8f0 100644 --- a/doc/11-cli-commands.md +++ b/doc/11-cli-commands.md @@ -22,7 +22,7 @@ Supported commands: * api setup (setup for API) * ca list (lists all certificate signing requests) * ca restore (restores a removed certificate request) - * ca remove (removes an outstanding certificate request) + * ca remove (removes an outstanding certificate request) * ca sign (signs an outstanding certificate request) * console (Icinga debug console) * daemon (starts Icinga 2) diff --git a/doc/12-icinga2-api.md b/doc/12-icinga2-api.md index e03785e24..471a628a2 100644 --- a/doc/12-icinga2-api.md +++ b/doc/12-icinga2-api.md @@ -115,7 +115,7 @@ You can also use [jq](https://stedolan.github.io/jq/) or `python -m json.tool` in combination with curl on the CLI. ```bash -curl ... | jq +curl ... | jq curl ... | python -m json.tool ``` @@ -1658,14 +1658,14 @@ Send a `POST` request to the URL endpoint `/v1/actions/execute-command`. --------------|------------|-------------- ttl | Number | **Required.** The time to live of the execution expressed in seconds. command_type | String | **Optional.** The command type: `CheckCommand` or `EventCommand` or `NotificationCommand`. Default: `EventCommand` - command | String | **Optional.** The command to execute. Its type must the same as `command_type`. It can be a macro string. Default: depending on the `command_type` it's either `$check_command$`, `$event_command$` or `$notification_command$` + command | String | **Optional.** The command to execute. Its type must the same as `command_type`. It can be a macro string. Default: depending on the `command_type` it's either `$check_command$`, `$event_command$` or `$notification_command$` endpoint | String | **Optional.** The endpoint to execute the command on. It can be a macro string. Default: `$command_endpoint$`. macros | Dictionary | **Optional.** Macro overrides. Default: `{}` - user | String | **Optional.** The user used for the notification command. + user | String | **Optional.** The user used for the notification command. notification | String | **Optional.** The notification used for the notification command. - + Example: - + ```bash curl -k -s -S -i -u root:icinga -H 'Accept: application/json' \ -X POST 'https://localhost:5665/v1/actions/execute-command' \ @@ -2638,7 +2638,7 @@ Name | Language | Description [BitBar for OSX](https://getbitbar.com/plugins/Dev/Icinga2/icinga2.24m.py) | Python | macOS tray app for highlighting the host/service status [Icinga 2 Multistatus](https://chrome.google.com/webstore/detail/icinga-multi-status/khabbhcojgkibdeipanmiphceeoiijal/related) | - | Chrome Extension [Naglite4](https://github.com/wftech/icinga2-naglite4) | Python | Naglite3 rewrite using the Icinga 2 REST API. -[icinga-telegram-bot](https://github.com/joni1993/icinga-telegram-bot) | Python | Telegram Bot using the Icinga 2 REST API +[icinga-telegram-bot](https://github.com/joni1993/icinga-telegram-bot) | Python | Telegram Bot using the Icinga 2 REST API ### Manage Objects diff --git a/doc/14-features.md b/doc/14-features.md index 19cb54b01..159cfc7f4 100644 --- a/doc/14-features.md +++ b/doc/14-features.md @@ -52,7 +52,7 @@ Icinga DB is a set of components for publishing, synchronizing and visualizing monitoring data in the Icinga ecosystem, consisting of: * Icinga 2 with its `icingadb` feature enabled, - responsible for publishing monitoring data to a Redis server, i.e. configuration and its runtime updates, + responsible for publishing monitoring data to a Redis server, i.e. configuration and its runtime updates, check results, state changes, downtimes, acknowledgements, notifications, and other events such as flapping * The [Icinga DB daemon](https://icinga.com/docs/icinga-db), which synchronizes the data between the Redis server and a database diff --git a/doc/18-library-reference.md b/doc/18-library-reference.md index feb5d5d43..592178664 100644 --- a/doc/18-library-reference.md +++ b/doc/18-library-reference.md @@ -1648,9 +1648,9 @@ Example: function set_x(val) { this.x = val } - + dict = {} - + set_x.call(dict, 7) /* Invokes set_x using `dict` as `this` */ ``` @@ -1671,7 +1671,7 @@ Example: function set_x(val) { this.x = val } - + var dict = {} var args = [ 7 ] diff --git a/doc/19-technical-concepts.md b/doc/19-technical-concepts.md index b3a056170..3f4b3812b 100644 --- a/doc/19-technical-concepts.md +++ b/doc/19-technical-concepts.md @@ -1887,7 +1887,7 @@ source | String | The execution UUID Special handling, calls `ClusterEvents::EnqueueCheck()` for command endpoint checks. This function enqueues check tasks into a queue which is controlled in `RemoteCheckThreadProc()`. -If the `endpoint` parameter is specified and is not equal to the local endpoint then the message is forwarded to the correct endpoint zone. +If the `endpoint` parameter is specified and is not equal to the local endpoint then the message is forwarded to the correct endpoint zone. ##### Permissions @@ -1932,7 +1932,7 @@ executions | Dictionary | Executions to be updated ##### Functions **Event Sender:** `ClusterEvents::ExecutedCommandAPIHandler`, `ClusterEvents::UpdateExecutionsAPIHandler`, `ApiActions::ExecuteCommand` -**Event Receiver:** `ClusterEvents::UpdateExecutionsAPIHandler` +**Event Receiver:** `ClusterEvents::UpdateExecutionsAPIHandler` ##### Permissions @@ -1962,7 +1962,7 @@ Key | Type | Description host | String | Host name. service | String | Service name. execution | String | The execution ID executed. -exitStatus | Number | The command exit status. +exitStatus | Number | The command exit status. output | String | The command output. start | Number | The unix timestamp at the start of the command execution end | Number | The unix timestamp at the end of the command execution @@ -1970,7 +1970,7 @@ end | Number | The unix timestamp at the end of the command ex ##### Functions **Event Sender:** `ClusterEvents::ExecuteCheckFromQueue`, `ClusterEvents::ExecuteCommandAPIHandler` -**Event Receiver:** `ClusterEvents::ExecutedCommandAPIHandler` +**Event Receiver:** `ClusterEvents::ExecutedCommandAPIHandler` ##### Permissions diff --git a/doc/22-selinux.md b/doc/22-selinux.md index 6c64c6f87..ae06037f2 100644 --- a/doc/22-selinux.md +++ b/doc/22-selinux.md @@ -116,19 +116,19 @@ The policy provides a role `icinga2adm_r` for confining an user which enables an SELinux is based on the least level of access required for a service to run. Using booleans you can grant more access in a defined way. The Icinga 2 policy package provides the following booleans. -**icinga2_can_connect_all** +**icinga2_can_connect_all** Having this boolean enabled allows icinga2 to connect to all ports. This can be necessary if you use features which connect to unconfined services, for example the [influxdb writer](14-features.md#influxdb-writer). -**icinga2_run_sudo** +**icinga2_run_sudo** To allow Icinga 2 executing plugins via sudo you can toogle this boolean. It is disabled by default, resulting in error messages like `execvpe(sudo) failed: Permission denied`. -**httpd_can_write_icinga2_command** +**httpd_can_write_icinga2_command** To allow httpd to write to the command pipe of icinga2 this boolean has to be enabled. This is enabled by default, if not needed you can disable it for more security. -**httpd_can_connect_icinga2_api** +**httpd_can_connect_icinga2_api** Enabling this boolean allows httpd to connect to the API of icinga2 (Ports labeled `icinga2_port_t`). This is enabled by default, if not needed you can disable it for more security. diff --git a/doc/23-migrating-from-icinga-1x.md b/doc/23-migrating-from-icinga-1x.md index 62b5667d4..14a30cf2e 100644 --- a/doc/23-migrating-from-icinga-1x.md +++ b/doc/23-migrating-from-icinga-1x.md @@ -804,7 +804,7 @@ define service { } ``` -Icinga 2 supports objects and (global) variables, but does not make a difference +Icinga 2 supports objects and (global) variables, but does not make a difference between the main configuration file or any other included file. icinga2.conf: diff --git a/doc/24-appendix.md b/doc/24-appendix.md index e0f0b2f2a..d04b9e3dd 100644 --- a/doc/24-appendix.md +++ b/doc/24-appendix.md @@ -692,4 +692,3 @@ the [servicegroups](24-appendix.md#schema-livestatus-servicegroups-table-attribu All [services](24-appendix.md#schema-livestatus-services-table-attributes) table attributes grouped with the [hostgroups](24-appendix.md#schema-livestatus-hostgroups-table-attributes) table prefixed with `hostgroup_`. - From 1cd515ef8a5d8cf181dbc8f261c0a4c86c2d7a2c Mon Sep 17 00:00:00 2001 From: Josef Friedrich Date: Thu, 12 Sep 2024 08:46:27 +0200 Subject: [PATCH 057/415] Fix typo in the api documentation --- doc/12-icinga2-api.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/12-icinga2-api.md b/doc/12-icinga2-api.md index e03785e24..d2175db09 100644 --- a/doc/12-icinga2-api.md +++ b/doc/12-icinga2-api.md @@ -2374,7 +2374,7 @@ Creation, modification and deletion of templates at runtime is not supported. ### Querying Templates You can request information about configuration templates by sending -a `GET` query to the `/v1/templates/` URL endpoint. `` URL endpoint. `` has to be replaced with the plural name of the object type you are interested in: From f3ed85171e90f8e6962ab0b37de9d8bcd89abdc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Mon, 16 Sep 2024 16:13:02 +0200 Subject: [PATCH 058/415] Linux Dev Environment: fix /usr/local/icinga2/etc ownership We instruct the users to build as root and chown just /usr/local/icinga2/var, but at least `icinga2 api setup` also needs to modify /usr/local/icinga2/etc. --- doc/21-development.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/21-development.md b/doc/21-development.md index cf7a54dd7..5b597cf18 100644 --- a/doc/21-development.md +++ b/doc/21-development.md @@ -1423,7 +1423,7 @@ The source installation doesn't set proper permissions, this is handled in the package builds which are officially supported. ```bash -chown -R icinga:icinga /usr/local/icinga2/var/ +chown -R icinga:icinga /usr/local/icinga2/{etc,var}/ /usr/local/icinga2/lib/icinga2/prepare-dirs /usr/local/icinga2/etc/sysconfig/icinga2 /usr/local/icinga2/sbin/icinga2 api setup @@ -1476,7 +1476,7 @@ The source installation doesn't set proper permissions, this is handled in the package builds which are officially supported. ```bash -chown -R icinga:icinga /usr/local/icinga2/var/ +chown -R icinga:icinga /usr/local/icinga2/{etc,var}/ /usr/local/icinga2/lib/icinga2/prepare-dirs /usr/local/icinga2/etc/sysconfig/icinga2 /usr/local/icinga2/sbin/icinga2 api setup @@ -1540,7 +1540,7 @@ The source installation doesn't set proper permissions, this is handled in the package builds which are officially supported. ```bash -chown -R icinga:icinga /usr/local/icinga2/var/ +chown -R icinga:icinga /usr/local/icinga2/{etc,var}/ /usr/local/icinga2/lib/icinga2/prepare-dirs /usr/local/icinga2/etc/sysconfig/icinga2 /usr/local/icinga2/sbin/icinga2 api setup From 6da948c83078098818afac6f1d28997a87e312d3 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 22 Aug 2024 12:58:14 +0200 Subject: [PATCH 059/415] doc/: don't mention CentOS It's EOL. --- doc/02-installation.md | 62 +------------------------ doc/02-installation.md.d/05-CentOS.md | 3 -- doc/03-monitoring-basics.md | 2 +- doc/05-service-monitoring.md | 4 +- doc/06-distributed-monitoring.md | 15 +++--- doc/11-cli-commands.md | 2 +- doc/14-features.md | 20 -------- doc/21-development.md | 67 +++++++++------------------ tools/selinux/icinga2.te | 2 +- 9 files changed, 36 insertions(+), 141 deletions(-) delete mode 100644 doc/02-installation.md.d/05-CentOS.md diff --git a/doc/02-installation.md b/doc/02-installation.md index 4b5956bc5..8c78e6c55 100644 --- a/doc/02-installation.md +++ b/doc/02-installation.md @@ -72,21 +72,6 @@ apt update ``` - -### CentOS Repository - -```bash -wget https://packages.icinga.com/centos/ICINGA-release.repo -O /etc/yum.repos.d/ICINGA-release.repo -``` - -The packages for CentOS depend on other packages which are distributed -as part of the [EPEL repository](https://fedoraproject.org/wiki/EPEL): - -```bash -yum install epel-release -``` - - ### RHEL Repository @@ -212,7 +197,7 @@ You can install Icinga 2 by using your distribution's package manager to install the `icinga2` package. The following commands must be executed with `root` permissions unless noted otherwise. - + !!! tip If you have [SELinux](22-selinux.md) enabled, the package `icinga2-selinux` is also required. @@ -227,21 +212,6 @@ apt install icinga2 ``` - - -#### CentOS - -!!! info - - Note that installing Icinga 2 is only supported on CentOS 7 as CentOS 8 is EOL. - -```bash -yum install icinga2 -systemctl enable icinga2 -systemctl start icinga2 -``` - - #### RHEL 8 or Later @@ -339,17 +309,6 @@ apt install monitoring-plugins ``` - - -#### CentOS - -The packages for CentOS depend on other packages which are distributed as part of the EPEL repository. - -```bash -yum install nagios-plugins-all -``` - - #### RHEL @@ -437,7 +396,7 @@ Restart Icinga 2 for these changes to take effect. systemctl restart icinga2 ``` - + ## Set up Icinga DB Icinga DB is a set of components for publishing, synchronizing and @@ -489,20 +448,6 @@ yum install icingadb-redis ``` - - -##### CentOS - - -!!! info - - Note that installing Icinga DB Redis is only supported on CentOS 7 as CentOS 8 is EOL. - -```bash -yum install icingadb-redis -``` - - ##### Debian / Ubuntu / Raspberry Pi OS @@ -616,9 +561,6 @@ you have completed the instructions here and can proceed to [install the Icinga DB daemon on Amazon Linux](https://icinga.com/docs/icinga-db/latest/doc/02-Installation/01-Amazon-Linux/#installing-icinga-db-package), - -[install the Icinga DB daemon on CentOS](https://icinga.com/docs/icinga-db/latest/doc/02-Installation/02-CentOS/#installing-icinga-db-package), - [install the Icinga DB daemon on Debian](https://icinga.com/docs/icinga-db/latest/doc/02-Installation/03-Debian/#installing-icinga-db-package), diff --git a/doc/02-installation.md.d/05-CentOS.md b/doc/02-installation.md.d/05-CentOS.md deleted file mode 100644 index 4d766b21c..000000000 --- a/doc/02-installation.md.d/05-CentOS.md +++ /dev/null @@ -1,3 +0,0 @@ -# Install Icinga 2 on CentOS - - diff --git a/doc/03-monitoring-basics.md b/doc/03-monitoring-basics.md index e534b0d01..f36b8ec59 100644 --- a/doc/03-monitoring-basics.md +++ b/doc/03-monitoring-basics.md @@ -2729,7 +2729,7 @@ Requirements: * Icinga 2 as client on the remote node * icinga user with sudo permissions to the httpd daemon -Example on CentOS 7: +Example on RHEL: ``` # visudo diff --git a/doc/05-service-monitoring.md b/doc/05-service-monitoring.md index 097fb1184..e798b303e 100644 --- a/doc/05-service-monitoring.md +++ b/doc/05-service-monitoring.md @@ -51,7 +51,7 @@ described. Try running the plugin after setup and [ensure it works](05-service-m Prior to using the check plugin with Icinga 2 you should ensure that it is working properly by trying to run it on the console using whichever user Icinga 2 is running as: -RHEL/CentOS/Fedora +RHEL/Fedora ```bash sudo -u icinga /usr/lib64/nagios/plugins/check_mysql_health --help @@ -111,7 +111,7 @@ Can't locate Net/SNMP.pm in @INC (you may need to install the Net::SNMP module) Prior to installing the Perl module via CPAN, look for a distribution specific package, e.g. `libnet-snmp-perl` on Debian/Ubuntu or `perl-Net-SNMP` -on RHEL/CentOS. +on RHEL. #### Optional: Custom Path diff --git a/doc/06-distributed-monitoring.md b/doc/06-distributed-monitoring.md index c72975713..1f24cb96f 100644 --- a/doc/06-distributed-monitoring.md +++ b/doc/06-distributed-monitoring.md @@ -264,7 +264,7 @@ The setup wizard will ensure that the following steps are taken: * Update the [ApiListener](06-distributed-monitoring.md#distributed-monitoring-apilistener) and [constants](04-configuration.md#constants-conf) configuration. * Update the [icinga2.conf](04-configuration.md#icinga2-conf) to disable the `conf.d` inclusion, and add the `api-users.conf` file inclusion. -Here is an example of a master setup for the `icinga2-master1.localdomain` node on CentOS 7: +Here is an example of a master setup for the `icinga2-master1.localdomain` node: ``` [root@icinga2-master1.localdomain /]# icinga2 node wizard @@ -1031,9 +1031,7 @@ in `/etc/icinga2/icinga2.conf`. > Defaults to disabled. Now it is time to validate the configuration and to restart the Icinga 2 daemon -on both nodes. - -Example on CentOS 7: +on both nodes: ``` [root@icinga2-agent1.localdomain /]# icinga2 daemon -C @@ -1112,7 +1110,8 @@ Save the changes and validate the configuration on the master node: ``` [root@icinga2-master1.localdomain /]# icinga2 daemon -C ``` -Restart the Icinga 2 daemon (example for CentOS 7): + +Restart the Icinga 2 daemon: ``` [root@icinga2-master1.localdomain /]# systemctl restart icinga2 @@ -1221,9 +1220,7 @@ object ApiListener "api" { ``` Now it is time to validate the configuration and to restart the Icinga 2 daemon -on both nodes. - -Example on CentOS 7: +on both nodes: ``` [root@icinga2-satellite1.localdomain /]# icinga2 daemon -C @@ -1285,7 +1282,7 @@ Save the changes and validate the configuration on the master node: [root@icinga2-master1.localdomain /]# icinga2 daemon -C ``` -Restart the Icinga 2 daemon (example for CentOS 7): +Restart the Icinga 2 daemon: ``` [root@icinga2-master1.localdomain /]# systemctl restart icinga2 diff --git a/doc/11-cli-commands.md b/doc/11-cli-commands.md index 457656cb0..47d14554b 100644 --- a/doc/11-cli-commands.md +++ b/doc/11-cli-commands.md @@ -73,7 +73,7 @@ RPM and Debian packages install the bash completion files into You need to install the `bash-completion` package if not already installed. -RHEL/CentOS/Fedora: +RHEL/Fedora: ```bash yum install bash-completion diff --git a/doc/14-features.md b/doc/14-features.md index 19cb54b01..84904313a 100644 --- a/doc/14-features.md +++ b/doc/14-features.md @@ -815,16 +815,6 @@ apt-get install icinga2-ido-mysql default. You can skip the automated setup and install/upgrade the database manually if you prefer. -###### CentOS 7 - -!!! info - - Note that installing `icinga2-ido-mysql` is only supported on CentOS 7 as CentOS 8 is EOL. - -```bash -yum install icinga2-ido-mysql -``` - ###### RHEL 8 ```bash @@ -914,16 +904,6 @@ apt-get install icinga2-ido-pgsql You can skip the automated setup and install/upgrade the database manually if you prefer that. -###### CentOS 7 - -!!! info - - Note that installing `icinga2-ido-pgsql` is only supported on CentOS 7 as CentOS 8 is EOL. - -```bash -yum install icinga2-ido-pgsql -``` - ###### RHEL 8 ```bash diff --git a/doc/21-development.md b/doc/21-development.md index cf7a54dd7..60f28527a 100644 --- a/doc/21-development.md +++ b/doc/21-development.md @@ -48,7 +48,7 @@ or `icinga2-ido-mysql`. Distribution | Command -------------------|------------------------------------------ Debian/Ubuntu | `apt-get install icinga2-dbg` -RHEL/CentOS | `yum install icinga2-debuginfo` +RHEL | `yum install icinga2-debuginfo` Fedora | `dnf install icinga2-debuginfo icinga2-bin-debuginfo icinga2-ido-mysql-debuginfo` SLES/openSUSE | `zypper install icinga2-bin-debuginfo icinga2-ido-mysql-debuginfo` @@ -65,7 +65,7 @@ Install GDB in your development environment. Distribution | Command -------------------|------------------------------------------ Debian/Ubuntu | `apt-get install gdb` -RHEL/CentOS | `yum install gdb` +RHEL | `yum install gdb` Fedora | `dnf install gdb` SLES/openSUSE | `zypper install gdb` @@ -537,7 +537,7 @@ packages. If you encounter a problem, please [open a new issue](https://github.com/Icinga/icinga2/issues/new/choose) on GitHub and mention that you're testing the snapshot packages. -#### RHEL/CentOS +#### RHEL 2.11+ requires the EPEL repository for Boost 1.66+. @@ -1332,9 +1332,6 @@ autocmd BufWinLeave * call clearmatches() ### Linux Dev Environment -Based on CentOS 7, we have an early draft available inside the Icinga Vagrant boxes: -[centos7-dev](https://github.com/Icinga/icinga-vagrant/tree/master/centos7-dev). - If you're compiling Icinga 2 natively without any virtualization layer in between, this usually is faster. This is also the reason why developers on macOS prefer native builds over Linux or Windows VMs. Don't forget to test the actual code on Linux later! Socket specific @@ -1357,21 +1354,20 @@ mkdir -p release debug Proceed with the specific distribution examples below. Keep in mind that these instructions are best effort and sometimes out-of-date. Git Master may contain updates. -* [CentOS 7](21-development.md#development-linux-dev-env-centos) +* [Fedora 40](21-development.md#development-linux-dev-env-fedora) * [Debian 10 Buster](21-development.md#development-linux-dev-env-debian) * [Ubuntu 18 Bionic](21-development.md#development-linux-dev-env-ubuntu) - -#### CentOS 7 +#### Fedora 40 ```bash -yum -y install gdb vim git bash-completion htop centos-release-scl +yum -y install gdb vim git bash-completion htop yum -y install rpmdevtools ccache \ - cmake make devtoolset-11-gcc-c++ flex bison \ - openssl-devel boost169-devel systemd-devel \ + cmake make gcc-c++ flex bison \ + openssl-devel boost-devel systemd-devel \ mysql-devel postgresql-devel libedit-devel \ - devtoolset-11-libstdc++-devel + libstdc++-devel groupadd icinga groupadd icingacmd @@ -1389,35 +1385,30 @@ slower but allows for better debugging insights. For benchmarks, change `CMAKE_BUILD_TYPE` to `RelWithDebInfo` and build inside the `release` directory. -First, off export some generics for Boost. +First, override the default prefix path. ```bash -export I2_BOOST="-DBoost_NO_BOOST_CMAKE=TRUE -DBoost_NO_SYSTEM_PATHS=TRUE -DBOOST_LIBRARYDIR=/usr/lib64/boost169 -DBOOST_INCLUDEDIR=/usr/include/boost169 -DBoost_ADDITIONAL_VERSIONS='1.69;1.69.0'" +export I2_GENERIC="-DCMAKE_INSTALL_PREFIX=/usr/local/icinga2" ``` -Second, add the prefix path to it. - -```bash -export I2_GENERIC="$I2_BOOST -DCMAKE_INSTALL_PREFIX=/usr/local/icinga2" -``` - -Third, define the two build types with their specific CMake variables. +Second, define the two build types with their specific CMake variables. ```bash export I2_DEBUG="-DCMAKE_BUILD_TYPE=Debug -DICINGA2_UNITY_BUILD=OFF $I2_GENERIC" export I2_RELEASE="-DCMAKE_BUILD_TYPE=RelWithDebInfo -DICINGA2_WITH_TESTS=ON -DICINGA2_UNITY_BUILD=ON $I2_GENERIC" ``` -Fourth, depending on your likings, you may add a bash alias for building, +Third, depending on your likings, you may use a bash alias for building, or invoke the commands inside: ```bash -alias i2_debug="cd /root/icinga2; mkdir -p debug; cd debug; scl enable devtoolset-11 -- cmake $I2_DEBUG ..; make -j2; sudo make -j2 install; cd .." -alias i2_release="cd /root/icinga2; mkdir -p release; cd release; scl enable devtoolset-11 -- cmake $I2_RELEASE ..; make -j2; sudo make -j2 install; cd .." +alias i2_debug="cd /root/icinga2; mkdir -p debug; cd debug; cmake $I2_DEBUG ..; make -j2; sudo make -j2 install; cd .." +alias i2_release="cd /root/icinga2; mkdir -p release; cd release; cmake $I2_RELEASE ..; make -j2; sudo make -j2 install; cd .." ``` -This is taken from the [centos7-dev](https://github.com/Icinga/icinga-vagrant/tree/master/centos7-dev) Vagrant box. - +```bash +i2_debug +``` The source installation doesn't set proper permissions, this is handled in the package builds which are officially supported. @@ -1429,7 +1420,7 @@ chown -R icinga:icinga /usr/local/icinga2/var/ /usr/local/icinga2/sbin/icinga2 api setup vim /usr/local/icinga2/etc/icinga2/conf.d/api-users.conf -/usr/local/icinga2/lib/icinga2/sbin/icinga2 daemon +/usr/local/icinga2/lib64/icinga2/sbin/icinga2 daemon ``` #### Debian 10 @@ -2203,7 +2194,7 @@ Icinga application using a dist tarball (including notes for distributions): * Debian/Ubuntu: libpq-dev * postgresql-dev on Alpine * libedit (CLI console) - * RHEL/Fedora: libedit-devel on CentOS (RHEL requires rhel-7-server-optional-rpms) + * RHEL/Fedora: libedit-devel (RHEL requires rhel-7-server-optional-rpms) * Debian/Ubuntu/Alpine: libedit-dev * Termcap (only required if libedit doesn't already link against termcap/ncurses) * RHEL/Fedora: libtermcap-devel @@ -2351,7 +2342,7 @@ can be used to disable the usage of `git describe`. ### Building RPMs -#### Build Environment on RHEL, CentOS, Fedora, Amazon Linux +#### Build Environment on RHEL, Fedora, Amazon Linux Setup your build environment: @@ -2407,7 +2398,7 @@ spectool -g ../SPECS/icinga2.spec cd $HOME/rpmbuild ``` -Install the build dependencies. Example for CentOS 7: +Install the build dependencies: ```bash yum -y install libedit-devel ncurses-devel gcc-c++ libstdc++-devel openssl-devel \ @@ -2436,21 +2427,9 @@ rpmbuild -ba SPECS/icinga2.spec The following packages are required to build the SELinux policy module: * checkpolicy -* selinux-policy (selinux-policy on CentOS 6, selinux-policy-devel on CentOS 7) +* selinux-policy-devel * selinux-policy-doc -##### RHEL/CentOS 7 - -The RedHat Developer Toolset is required for building Icinga 2 beforehand. -This contains a C++ compiler which supports C++17 features. - -```bash -yum install centos-release-scl -``` - -Dependencies to devtools-11 are used in the RPM SPEC, so the correct tools -should be used for building. - ##### Amazon Linux If you prefer to build packages offline, a suitable Vagrant box is located diff --git a/tools/selinux/icinga2.te b/tools/selinux/icinga2.te index 1b9f0513f..0f50908da 100644 --- a/tools/selinux/icinga2.te +++ b/tools/selinux/icinga2.te @@ -205,7 +205,7 @@ corenet_tcp_connect_lmtp_port(icinga2_t) # Allow icinga2 to connect to redis using unix domain sockets stream_connect_pattern(icinga2_t, redis_var_run_t, redis_var_run_t, redis_t) -# Just like `redis_tcp_connect(icinga2_t)`, though this interface does not exist on centos7 +# Just like `redis_tcp_connect(icinga2_t)`, though this interface does not exist on Amazon Linux 2 corenet_tcp_recvfrom_labeled(icinga2_t, redis_t) corenet_tcp_sendrecv_redis_port(icinga2_t) corenet_tcp_connect_redis_port(icinga2_t) From 36742c27b94bec45d94bd9482bdeeeabc576eecb Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 18 Sep 2024 15:19:32 +0200 Subject: [PATCH 060/415] Don't override Type#GetLoadDependencies() if latter is sufficient If a specific type shall return no deps, the base method already does that. --- tools/mkclass/classcompiler.cpp | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/tools/mkclass/classcompiler.cpp b/tools/mkclass/classcompiler.cpp index 3a49576b8..0a6596078 100644 --- a/tools/mkclass/classcompiler.cpp +++ b/tools/mkclass/classcompiler.cpp @@ -376,19 +376,21 @@ void ClassCompiler::HandleClass(const Klass& klass, const ClassDebugInfo&) << "}" << std::endl << std::endl; /* GetLoadDependencies */ - m_Header << "\t" << "const std::unordered_set& GetLoadDependencies() const override;" << std::endl; + if (!klass.LoadDependencies.empty()) { + m_Header << "\tconst std::unordered_set& GetLoadDependencies() const override;" << std::endl; - m_Impl << "const std::unordered_set& TypeImpl<" << klass.Name << ">::GetLoadDependencies() const" << std::endl - << "{" << std::endl - << "\t" << "static const std::unordered_set deps ({" << std::endl; + m_Impl << "const std::unordered_set& TypeImpl<" << klass.Name << ">::GetLoadDependencies() const" << std::endl + << "{" << std::endl + << "\tstatic const std::unordered_set deps ({" << std::endl; - for (const std::string& dep : klass.LoadDependencies) - m_Impl << "\t\t" << "GetByName(\"" << dep << "\").get()," << std::endl; + for (const std::string& dep : klass.LoadDependencies) + m_Impl << "\t\tGetByName(\"" << dep << "\").get()," << std::endl; - m_Impl << "\t" << "});" << std::endl; + m_Impl << "\t});" << std::endl; - m_Impl << "\t" << "return deps;" << std::endl - << "}" << std::endl << std::endl; + m_Impl << "\treturn deps;" << std::endl + << "}" << std::endl << std::endl; + } /* GetActivationPriority */ m_Header << "\t" << "int GetActivationPriority() const override;" << std::endl; From c24713ac104512df6238c97747ebd6698160beb7 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 19 Sep 2024 16:16:44 +0200 Subject: [PATCH 061/415] Type#GetLoadDependencies(): VERIFY() that no nullptr is returned --- tools/mkclass/classcompiler.cpp | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/tools/mkclass/classcompiler.cpp b/tools/mkclass/classcompiler.cpp index 0a6596078..f85719ac5 100644 --- a/tools/mkclass/classcompiler.cpp +++ b/tools/mkclass/classcompiler.cpp @@ -381,14 +381,25 @@ void ClassCompiler::HandleClass(const Klass& klass, const ClassDebugInfo&) m_Impl << "const std::unordered_set& TypeImpl<" << klass.Name << ">::GetLoadDependencies() const" << std::endl << "{" << std::endl - << "\tstatic const std::unordered_set deps ({" << std::endl; + << "\tstatic const auto deps ([] {" << std::endl; + + for (auto& dep : klass.LoadDependencies) + m_Impl << "\t\tauto type" << dep << " (GetByName(\"" << dep << "\").get());" << std::endl; + + m_Impl << std::endl; + + for (auto& dep : klass.LoadDependencies) + m_Impl << "\t\tVERIFY(type" << dep << ");" << std::endl; + + m_Impl << std::endl + << "\t\treturn std::unordered_set{"; for (const std::string& dep : klass.LoadDependencies) - m_Impl << "\t\tGetByName(\"" << dep << "\").get()," << std::endl; + m_Impl << " type" << dep << ","; - m_Impl << "\t});" << std::endl; - - m_Impl << "\treturn deps;" << std::endl + m_Impl << " };" << std::endl + << "\t}());" << std::endl << std::endl + << "\treturn deps;" << std::endl << "}" << std::endl << std::endl; } @@ -1465,6 +1476,7 @@ void ClassCompiler::CompileStream(const std::string& path, std::istream& input, << "#include \"base/objectlock.hpp\"" << std::endl << "#include \"base/utility.hpp\"" << std::endl << "#include \"base/convert.hpp\"" << std::endl + << "#include \"base/debug.hpp\"" << std::endl << "#include \"base/dependencygraph.hpp\"" << std::endl << "#include \"base/logger.hpp\"" << std::endl << "#include \"base/function.hpp\"" << std::endl From 4b20121dcf3c556c35c67e79d79c2affa171163f Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 20 Sep 2024 14:30:40 +0200 Subject: [PATCH 062/415] Type#GetLoadDependencies(): group operations by type --- tools/mkclass/classcompiler.cpp | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/tools/mkclass/classcompiler.cpp b/tools/mkclass/classcompiler.cpp index f85719ac5..0499b4a97 100644 --- a/tools/mkclass/classcompiler.cpp +++ b/tools/mkclass/classcompiler.cpp @@ -384,15 +384,10 @@ void ClassCompiler::HandleClass(const Klass& klass, const ClassDebugInfo&) << "\tstatic const auto deps ([] {" << std::endl; for (auto& dep : klass.LoadDependencies) - m_Impl << "\t\tauto type" << dep << " (GetByName(\"" << dep << "\").get());" << std::endl; + m_Impl << "\t\tauto type" << dep << " (GetByName(\"" << dep << "\").get());" << std::endl + << "\t\tVERIFY(type" << dep << ");" << std::endl << std::endl; - m_Impl << std::endl; - - for (auto& dep : klass.LoadDependencies) - m_Impl << "\t\tVERIFY(type" << dep << ");" << std::endl; - - m_Impl << std::endl - << "\t\treturn std::unordered_set{"; + m_Impl << "\t\treturn std::unordered_set{"; for (const std::string& dep : klass.LoadDependencies) m_Impl << " type" << dep << ","; From b6517c69732bdbeb79ca9eafba350f93a991103d Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 20 Sep 2024 14:34:16 +0200 Subject: [PATCH 063/415] Type#GetLoadDependencies(): VERIFY() that only config object types are returned --- tools/mkclass/classcompiler.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tools/mkclass/classcompiler.cpp b/tools/mkclass/classcompiler.cpp index 0499b4a97..6082cbed6 100644 --- a/tools/mkclass/classcompiler.cpp +++ b/tools/mkclass/classcompiler.cpp @@ -385,7 +385,8 @@ void ClassCompiler::HandleClass(const Klass& klass, const ClassDebugInfo&) for (auto& dep : klass.LoadDependencies) m_Impl << "\t\tauto type" << dep << " (GetByName(\"" << dep << "\").get());" << std::endl - << "\t\tVERIFY(type" << dep << ");" << std::endl << std::endl; + << "\t\tVERIFY(type" << dep << ");" << std::endl + << "\t\tVERIFY(ConfigObject::TypeInstance->IsAssignableFrom(type" << dep << "));" << std::endl << std::endl; m_Impl << "\t\treturn std::unordered_set{"; @@ -1475,6 +1476,7 @@ void ClassCompiler::CompileStream(const std::string& path, std::istream& input, << "#include \"base/dependencygraph.hpp\"" << std::endl << "#include \"base/logger.hpp\"" << std::endl << "#include \"base/function.hpp\"" << std::endl + << "#include \"base/configobject.hpp\"" << std::endl << "#include \"base/configtype.hpp\"" << std::endl << "#ifdef _MSC_VER" << std::endl << "#pragma warning( push )" << std::endl From b848934d5792178ed89bd4a6d86e664db686ab3e Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 2 Sep 2024 17:34:47 +0200 Subject: [PATCH 064/415] Introduce Type::GetConfigTypesSortedByLoadDependencies() --- lib/base/initialize.hpp | 1 + lib/base/type.cpp | 65 +++++++++++++++++++++++++++++++++++++++++ lib/base/type.hpp | 1 + 3 files changed, 67 insertions(+) diff --git a/lib/base/initialize.hpp b/lib/base/initialize.hpp index adc995fe1..6c50b2408 100644 --- a/lib/base/initialize.hpp +++ b/lib/base/initialize.hpp @@ -23,6 +23,7 @@ enum class InitializePriority { RegisterBuiltinTypes, RegisterFunctions, RegisterTypes, + SortTypes, EvaluateConfigFragments, Default, FreezeNamespaces, diff --git a/lib/base/type.cpp b/lib/base/type.cpp index 1dcef39bf..70dd6333e 100644 --- a/lib/base/type.cpp +++ b/lib/base/type.cpp @@ -1,9 +1,15 @@ /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ #include "base/type.hpp" +#include "base/atomic.hpp" +#include "base/configobject.hpp" +#include "base/debug.hpp" #include "base/scriptglobal.hpp" #include "base/namespace.hpp" #include "base/objectlock.hpp" +#include +#include +#include using namespace icinga; @@ -32,6 +38,59 @@ INITIALIZE_ONCE_WITH_PRIORITY([]() { Type::Register(type); }, InitializePriority::RegisterTypeType); +static std::vector l_SortedByLoadDependencies; +static Atomic l_SortingByLoadDependenciesDone (false); + +typedef std::unordered_map Visited; // https://stackoverflow.com/a/8942986 + +INITIALIZE_ONCE_WITH_PRIORITY([] { + auto types (Type::GetAllTypes()); + + types.erase(std::remove_if(types.begin(), types.end(), [](auto& type) { + return !ConfigObject::TypeInstance->IsAssignableFrom(type); + }), types.end()); + + // Depth-first search + std::unordered_set unsorted; + Visited visited; + std::vector sorted; + + for (auto type : types) { + unsorted.emplace(type.get()); + } + + std::function visit ([&visit, &unsorted, &visited, &sorted](Type* type) { + if (unsorted.find(type) == unsorted.end()) { + return; + } + + bool& alreadyVisited (visited.at(type)); + VERIFY(!alreadyVisited); + alreadyVisited = true; + + for (auto dep : type->GetLoadDependencies()) { + visit(dep); + } + + unsorted.erase(type); + sorted.emplace_back(type); + }); + + while (!unsorted.empty()) { + for (auto& type : types) { + visited[type.get()] = false; + } + + visit(*unsorted.begin()); + } + + VERIFY(sorted.size() == types.size()); + VERIFY(sorted[0]->GetLoadDependencies().empty()); + + std::swap(sorted, l_SortedByLoadDependencies); + l_SortingByLoadDependenciesDone.store(true); +}, InitializePriority::SortTypes); + String Type::ToString() const { return "type '" + GetName() + "'"; @@ -72,6 +131,12 @@ std::vector Type::GetAllTypes() return types; } +const std::vector& Type::GetConfigTypesSortedByLoadDependencies() +{ + VERIFY(l_SortingByLoadDependenciesDone.load()); + return l_SortedByLoadDependencies; +} + String Type::GetPluralName() const { String name = GetName(); diff --git a/lib/base/type.hpp b/lib/base/type.hpp index 7b8d1cacb..fa78f858e 100644 --- a/lib/base/type.hpp +++ b/lib/base/type.hpp @@ -82,6 +82,7 @@ public: static void Register(const Type::Ptr& type); static Type::Ptr GetByName(const String& name); static std::vector GetAllTypes(); + static const std::vector& GetConfigTypesSortedByLoadDependencies(); void SetField(int id, const Value& value, bool suppress_events = false, const Value& cookie = Empty) override; Value GetField(int id) const override; From 31f3acaa137dd124d62c9baea156f19985a12b77 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 15 Feb 2024 11:46:27 +0100 Subject: [PATCH 065/415] ConfigItem::CommitNewItems(): pre-sort types by their load dependencies once to avoid complicated nested loops, iterating over the same types and checking dependencies over and over, skipping already completed ones. --- lib/config/configitem.cpp | 229 +++++++++++++++----------------------- 1 file changed, 90 insertions(+), 139 deletions(-) diff --git a/lib/config/configitem.cpp b/lib/config/configitem.cpp index bf4da81a4..e8a509275 100644 --- a/lib/config/configitem.cpp +++ b/lib/config/configitem.cpp @@ -444,74 +444,47 @@ bool ConfigItem::CommitNewItems(const ActivationContext::Ptr& context, WorkQueue << "Committing " << total << " new items."; #endif /* I2_DEBUG */ - std::set types; - std::set completed_types; int itemsCount {0}; - for (const Type::Ptr& type : Type::GetAllTypes()) { - if (ConfigObject::TypeInstance->IsAssignableFrom(type)) - types.insert(type); - } + for (auto& type : Type::GetConfigTypesSortedByLoadDependencies()) { + std::atomic committed_items(0); - while (types.size() != completed_types.size()) { - for (const Type::Ptr& type : types) { - if (completed_types.find(type) != completed_types.end()) - continue; + { + auto items (itemsByType.find(type.get())); - bool unresolved_dep = false; - - /* skip this type (for now) if there are unresolved load dependencies */ - for (auto pLoadDep : type->GetLoadDependencies()) { - if (types.find(pLoadDep) != types.end() && completed_types.find(pLoadDep) == completed_types.end()) { - unresolved_dep = true; - break; + if (items != itemsByType.end()) { + for (const ItemPair& pair: items->second) { + newItems.emplace_back(pair.first); } - } - if (unresolved_dep) - continue; + upq.ParallelFor(items->second, [&committed_items](const ItemPair& ip) { + const ConfigItem::Ptr& item = ip.first; - std::atomic committed_items(0); - - { - auto items (itemsByType.find(type.get())); - - if (items != itemsByType.end()) { - for (const ItemPair& pair: items->second) { - newItems.emplace_back(pair.first); - } - - upq.ParallelFor(items->second, [&committed_items](const ItemPair& ip) { - const ConfigItem::Ptr& item = ip.first; - - if (!item->Commit(ip.second)) { - if (item->IsIgnoreOnError()) { - item->Unregister(); - } - - return; + if (!item->Commit(ip.second)) { + if (item->IsIgnoreOnError()) { + item->Unregister(); } - committed_items++; - }); + return; + } - upq.Join(); - } + committed_items++; + }); + + upq.Join(); } + } - itemsCount += committed_items; - - completed_types.insert(type); + itemsCount += committed_items; #ifdef I2_DEBUG - if (committed_items > 0) - Log(LogDebug, "configitem") - << "Committed " << committed_items << " items of type '" << type->GetName() << "'."; + if (committed_items > 0) + Log(LogDebug, "configitem") + << "Committed " << committed_items << " items of type '" << type->GetName() << "'."; #endif /* I2_DEBUG */ - if (upq.HasExceptions()) - return false; - } + if (upq.HasExceptions()) + return false; } #ifdef I2_DEBUG @@ -519,105 +492,83 @@ bool ConfigItem::CommitNewItems(const ActivationContext::Ptr& context, WorkQueue << "Committed " << itemsCount << " items."; #endif /* I2_DEBUG */ - completed_types.clear(); + for (auto& type : Type::GetConfigTypesSortedByLoadDependencies()) { + std::atomic notified_items(0); - while (types.size() != completed_types.size()) { - for (const Type::Ptr& type : types) { - if (completed_types.find(type) != completed_types.end()) - continue; + { + auto items (itemsByType.find(type.get())); - bool unresolved_dep = false; + if (items != itemsByType.end()) { + upq.ParallelFor(items->second, [¬ified_items](const ItemPair& ip) { + const ConfigItem::Ptr& item = ip.first; - /* skip this type (for now) if there are unresolved load dependencies */ - for (auto pLoadDep : type->GetLoadDependencies()) { - if (types.find(pLoadDep) != types.end() && completed_types.find(pLoadDep) == completed_types.end()) { - unresolved_dep = true; - break; - } - } + if (!item->m_Object) + return; - if (unresolved_dep) - continue; - - std::atomic notified_items(0); - - { - auto items (itemsByType.find(type.get())); - - if (items != itemsByType.end()) { - upq.ParallelFor(items->second, [¬ified_items](const ItemPair& ip) { - const ConfigItem::Ptr& item = ip.first; - - if (!item->m_Object) - return; - - try { - item->m_Object->OnAllConfigLoaded(); - notified_items++; - } catch (const std::exception& ex) { - if (!item->m_IgnoreOnError) - throw; - - Log(LogNotice, "ConfigObject") - << "Ignoring config object '" << item->m_Name << "' of type '" << item->m_Type->GetName() << "' due to errors: " << DiagnosticInformation(ex); - - item->Unregister(); - - { - std::unique_lock lock(item->m_Mutex); - item->m_IgnoredItems.push_back(item->m_DebugInfo.Path); - } - } - }); - - upq.Join(); - } - } - - completed_types.insert(type); - -#ifdef I2_DEBUG - if (notified_items > 0) - Log(LogDebug, "configitem") - << "Sent OnAllConfigLoaded to " << notified_items << " items of type '" << type->GetName() << "'."; -#endif /* I2_DEBUG */ - - if (upq.HasExceptions()) - return false; - - notified_items = 0; - for (auto loadDep : type->GetLoadDependencies()) { - auto items (itemsByType.find(loadDep)); - - if (items != itemsByType.end()) { - upq.ParallelFor(items->second, [&type, ¬ified_items](const ItemPair& ip) { - const ConfigItem::Ptr& item = ip.first; - - if (!item->m_Object) - return; - - ActivationScope ascope(item->m_ActivationContext); - item->m_Object->CreateChildObjects(type); + try { + item->m_Object->OnAllConfigLoaded(); notified_items++; - }); - } - } + } catch (const std::exception& ex) { + if (!item->m_IgnoreOnError) + throw; - upq.Join(); + Log(LogNotice, "ConfigObject") + << "Ignoring config object '" << item->m_Name << "' of type '" << item->m_Type->GetName() << "' due to errors: " << DiagnosticInformation(ex); + + item->Unregister(); + + { + std::unique_lock lock(item->m_Mutex); + item->m_IgnoredItems.push_back(item->m_DebugInfo.Path); + } + } + }); + + upq.Join(); + } + } #ifdef I2_DEBUG - if (notified_items > 0) - Log(LogDebug, "configitem") - << "Sent CreateChildObjects to " << notified_items << " items of type '" << type->GetName() << "'."; + if (notified_items > 0) + Log(LogDebug, "configitem") + << "Sent OnAllConfigLoaded to " << notified_items << " items of type '" << type->GetName() << "'."; #endif /* I2_DEBUG */ - if (upq.HasExceptions()) - return false; + if (upq.HasExceptions()) + return false; - // Make sure to activate any additionally generated items - if (!CommitNewItems(context, upq, newItems)) - return false; + notified_items = 0; + for (auto loadDep : type->GetLoadDependencies()) { + auto items (itemsByType.find(loadDep)); + + if (items != itemsByType.end()) { + upq.ParallelFor(items->second, [&type, ¬ified_items](const ItemPair& ip) { + const ConfigItem::Ptr& item = ip.first; + + if (!item->m_Object) + return; + + ActivationScope ascope(item->m_ActivationContext); + item->m_Object->CreateChildObjects(type); + notified_items++; + }); + } } + + upq.Join(); + +#ifdef I2_DEBUG + if (notified_items > 0) + Log(LogDebug, "configitem") + << "Sent CreateChildObjects to " << notified_items << " items of type '" << type->GetName() << "'."; +#endif /* I2_DEBUG */ + + if (upq.HasExceptions()) + return false; + + // Make sure to activate any additionally generated items + if (!CommitNewItems(context, upq, newItems)) + return false; } return true; From 467e8b18e7ca9b246ca2e8b5afeaa7b5cc6392b3 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 9 Sep 2024 17:25:13 +0200 Subject: [PATCH 066/415] Type: Simplify sort by load dependencies algorithm --- lib/base/type.cpp | 56 ++++++++++++++++------------------------------- lib/base/type.hpp | 14 ++++++++++++ 2 files changed, 33 insertions(+), 37 deletions(-) diff --git a/lib/base/type.cpp b/lib/base/type.cpp index 70dd6333e..f6539b7c4 100644 --- a/lib/base/type.cpp +++ b/lib/base/type.cpp @@ -7,9 +7,7 @@ #include "base/scriptglobal.hpp" #include "base/namespace.hpp" #include "base/objectlock.hpp" -#include #include -#include using namespace icinga; @@ -41,53 +39,37 @@ INITIALIZE_ONCE_WITH_PRIORITY([]() { static std::vector l_SortedByLoadDependencies; static Atomic l_SortingByLoadDependenciesDone (false); -typedef std::unordered_map Visited; // https://stackoverflow.com/a/8942986 - INITIALIZE_ONCE_WITH_PRIORITY([] { - auto types (Type::GetAllTypes()); + std::unordered_set visited; - types.erase(std::remove_if(types.begin(), types.end(), [](auto& type) { - return !ConfigObject::TypeInstance->IsAssignableFrom(type); - }), types.end()); - - // Depth-first search - std::unordered_set unsorted; - Visited visited; - std::vector sorted; - - for (auto type : types) { - unsorted.emplace(type.get()); - } - - std::function visit ([&visit, &unsorted, &visited, &sorted](Type* type) { - if (unsorted.find(type) == unsorted.end()) { + std::function visit; + // Please note that this callback does not detect any cyclic load dependencies, + // instead, it relies on the "sort_by_load_after" unit test to fail. + visit = ([&visit, &visited](Type* type) { + if (visited.find(type) != visited.end()) { return; } + visited.emplace(type); - bool& alreadyVisited (visited.at(type)); - VERIFY(!alreadyVisited); - alreadyVisited = true; - - for (auto dep : type->GetLoadDependencies()) { - visit(dep); + for (auto dependency : type->GetLoadDependencies()) { + visit(dependency); } - unsorted.erase(type); - sorted.emplace_back(type); + // We have managed to reach the final/top node in this dependency graph, + // so let's place them in reverse order to their final place. + l_SortedByLoadDependencies.emplace_back(type); }); - while (!unsorted.empty()) { - for (auto& type : types) { - visited[type.get()] = false; + // Sort the types by their load_after dependencies in a Depth-First search manner. + for (const Type::Ptr& type : Type::GetAllTypes()) { + // Note that only those types that are assignable to the dynamic ConfigObject type can have "load_after" + // dependencies, otherwise they are just some Icinga 2 primitive types such as Number, String, etc. and + // we need to ignore them. + if (ConfigObject::TypeInstance->IsAssignableFrom(type)) { + visit(type.get()); } - - visit(*unsorted.begin()); } - VERIFY(sorted.size() == types.size()); - VERIFY(sorted[0]->GetLoadDependencies().empty()); - - std::swap(sorted, l_SortedByLoadDependencies); l_SortingByLoadDependenciesDone.store(true); }, InitializePriority::SortTypes); diff --git a/lib/base/type.hpp b/lib/base/type.hpp index fa78f858e..ee8a9b37c 100644 --- a/lib/base/type.hpp +++ b/lib/base/type.hpp @@ -82,6 +82,20 @@ public: static void Register(const Type::Ptr& type); static Type::Ptr GetByName(const String& name); static std::vector GetAllTypes(); + + /** + * Returns a list of config types sorted by their "load_after" dependencies. + * + * All dependencies of a given type are listed at a lower index than that of the type itself. In other words, + * if a `Service` type load depends on the `Host` and `ApiListener` types, the Host and ApiListener types are + * guaranteed to appear first on the list. Nevertheless, the order of the Host and ApiListener types themselves + * is arbitrary if the two types are not dependent. + * + * It should be noted that this method will fail fatally when used prior to the completion + * of namespace initialization. + * + * @return std::vector + */ static const std::vector& GetConfigTypesSortedByLoadDependencies(); void SetField(int id, const Value& value, bool suppress_events = false, const Value& cookie = Empty) override; From eb97676d699cc326fc7b07b29c60a2482bf35295 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 10 Sep 2024 09:28:28 +0200 Subject: [PATCH 067/415] Add basic test cases for `Type::GetConfigTypesSortedByLoadDependencies()` --- test/CMakeLists.txt | 58 +++++++++++++++++++++++++++++++++++++++++---- test/base-type.cpp | 33 +++++++++++++++++++++++++- 2 files changed, 85 insertions(+), 6 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e3772886a..dd9724f0b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -2,6 +2,59 @@ include(BoostTestTargets) +set(types_test_SOURCES + icingaapplication-fixture.cpp + base-type.cpp + ${base_OBJS} + $ + $ + $ + $ +) + +if(ICINGA2_WITH_CHECKER) + list(APPEND types_test_SOURCES $) +endif() + +if(ICINGA2_WITH_MYSQL) + list(APPEND types_test_SOURCES $ $) +endif() + +if(ICINGA2_WITH_PGSQL) + list(APPEND types_test_SOURCES $ $) +endif() + +if(ICINGA2_WITH_ICINGADB) + list(APPEND types_test_SOURCES $) +endif() + +if(ICINGA2_WITH_NOTIFICATION) + list(APPEND types_test_SOURCES $) +endif() + +if(ICINGA2_WITH_PERFDATA) + list(APPEND types_test_SOURCES $) +endif() + +if(ICINGA2_UNITY_BUILD) + mkunity_target(types test types_test_SOURCES) +endif() + +# In order to test the order of all Icinga 2 config type load dependencies, we need to link against all the libraries, +# but this results in boost signals e.g. in dbevents.cpp being triggered by icinga-checkresult.cpp test cases that +# only pass partially initialised objects. Therefore, the types test cases are decoupled from base and moved to a +# separate executable to not crash the base test cases. +add_boost_test(types + SOURCES test-runner.cpp ${types_test_SOURCES} + LIBRARIES ${base_DEPS} + TESTS + types/gettype + types/assign + types/byname + types/instantiate + types/sort_by_load_after +) + set(base_test_SOURCES icingaapplication-fixture.cpp base-array.cpp @@ -21,7 +74,6 @@ set(base_test_SOURCES base-string.cpp base-timer.cpp base-tlsutility.cpp - base-type.cpp base-utility.cpp base-value.cpp config-apply.cpp @@ -117,10 +169,6 @@ add_boost_test(base base_tlsutility/iscertuptodate_ok base_tlsutility/iscertuptodate_expiring base_tlsutility/iscertuptodate_old - base_type/gettype - base_type/assign - base_type/byname - base_type/instantiate base_utility/parse_version base_utility/compare_version base_utility/comparepasswords_works diff --git a/test/base-type.cpp b/test/base-type.cpp index 21bcf439d..4a8d0deb4 100644 --- a/test/base-type.cpp +++ b/test/base-type.cpp @@ -5,11 +5,13 @@ #include "base/objectlock.hpp" #include "base/application.hpp" #include "base/type.hpp" +#include "icinga/host.hpp" +#include "icinga/service.hpp" #include using namespace icinga; -BOOST_AUTO_TEST_SUITE(base_type) +BOOST_AUTO_TEST_SUITE(types) BOOST_AUTO_TEST_CASE(gettype) { @@ -44,4 +46,33 @@ BOOST_AUTO_TEST_CASE(instantiate) BOOST_CHECK(p); } +BOOST_AUTO_TEST_CASE(sort_by_load_after) +{ + int totalDependencies{0}; + std::unordered_set previousTypes; + for (auto type : Type::GetConfigTypesSortedByLoadDependencies()) { + BOOST_CHECK_EQUAL(true, ConfigObject::TypeInstance->IsAssignableFrom(type)); + + totalDependencies += type->GetLoadDependencies().size(); + for (Type* dependency : type->GetLoadDependencies()) { + // Note, Type::GetConfigTypesSortedByLoadDependencies() does not detect any cyclic load dependencies, + // instead, it relies on this test case to fail. + BOOST_CHECK_MESSAGE(previousTypes.find(dependency) != previousTypes.end(), "type '" << type->GetName() + << "' depends on '"<< dependency->GetName() << "' type, but it's not loaded before"); + } + + previousTypes.emplace(type.get()); + } + + // The magic number 12 is the sum of host,service,comment,downtime and endpoint load_after dependencies. + BOOST_CHECK_MESSAGE(totalDependencies >= 12, "total size of load dependency must be at least 12"); + BOOST_CHECK_MESSAGE(previousTypes.find(Host::TypeInstance.get()) != previousTypes.end(), "Host type should be in the list"); + BOOST_CHECK_MESSAGE(previousTypes.find(Service::TypeInstance.get()) != previousTypes.end(), "Service type should be in the list"); + BOOST_CHECK_MESSAGE(previousTypes.find(Downtime::TypeInstance.get()) != previousTypes.end(), "Downtime type should be in the list"); + BOOST_CHECK_MESSAGE(previousTypes.find(Comment::TypeInstance.get()) != previousTypes.end(), "Comment type should be in the list"); + BOOST_CHECK_MESSAGE(previousTypes.find(Notification::TypeInstance.get()) != previousTypes.end(), "Notification type should be in the list"); + BOOST_CHECK_MESSAGE(previousTypes.find(Zone::TypeInstance.get()) != previousTypes.end(), "Zone type should be in the list"); + BOOST_CHECK_MESSAGE(previousTypes.find(Endpoint::TypeInstance.get()) != previousTypes.end(), "Endpoint type should be in the list"); +} + BOOST_AUTO_TEST_SUITE_END() From 7216220de13e73a56c51c3db2cdcb73a63c23f92 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 20 Sep 2024 17:39:26 +0200 Subject: [PATCH 068/415] Bump OpenSSL shipped for Windows to v3.0.15 --- doc/win-dev.ps1 | 2 +- tools/win32/configure.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/win-dev.ps1 b/doc/win-dev.ps1 index 93e9d41b7..f64017bb0 100644 --- a/doc/win-dev.ps1 +++ b/doc/win-dev.ps1 @@ -14,7 +14,7 @@ function ThrowOnNativeFailure { $VsVersion = 2019 $MsvcVersion = '14.2' $BoostVersion = @(1, 86, 0) -$OpensslVersion = '3_0_14' +$OpensslVersion = '3_0_15' switch ($Env:BITS) { 32 { } diff --git a/tools/win32/configure.ps1 b/tools/win32/configure.ps1 index 9871d0e8e..1fb83c118 100644 --- a/tools/win32/configure.ps1 +++ b/tools/win32/configure.ps1 @@ -30,7 +30,7 @@ if (-not (Test-Path env:CMAKE_GENERATOR_PLATFORM)) { } } if (-not (Test-Path env:OPENSSL_ROOT_DIR)) { - $env:OPENSSL_ROOT_DIR = "c:\local\OpenSSL_3_0_14-Win${env:BITS}" + $env:OPENSSL_ROOT_DIR = "c:\local\OpenSSL_3_0_15-Win${env:BITS}" } if (-not (Test-Path env:BOOST_ROOT)) { $env:BOOST_ROOT = "c:\local\boost_1_86_0-Win${env:BITS}" From 8c68c6e9d81bc49e9c3ecd8860e921b3e755b84c Mon Sep 17 00:00:00 2001 From: Sebastian Grund Date: Wed, 25 Sep 2024 13:03:00 +0200 Subject: [PATCH 069/415] Add closing quotationmarks in Validator for influxdb writer config --- lib/perfdata/influxdbcommonwriter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/perfdata/influxdbcommonwriter.cpp b/lib/perfdata/influxdbcommonwriter.cpp index fc3786c89..d5aaa7c98 100644 --- a/lib/perfdata/influxdbcommonwriter.cpp +++ b/lib/perfdata/influxdbcommonwriter.cpp @@ -571,7 +571,7 @@ void InfluxdbCommonWriter::ValidateHostTemplate(const Lazy& lva ObjectLock olock(tags); for (const Dictionary::Pair& pair : tags) { if (!MacroProcessor::ValidateMacroString(pair.second)) - BOOST_THROW_EXCEPTION(ValidationError(this, { "host_template", "tags", pair.first }, "Closing $ not found in macro format string '" + pair.second)); + BOOST_THROW_EXCEPTION(ValidationError(this, { "host_template", "tags", pair.first }, "Closing $ not found in macro format string '" + pair.second + "'.")); } } } @@ -589,7 +589,7 @@ void InfluxdbCommonWriter::ValidateServiceTemplate(const Lazy& ObjectLock olock(tags); for (const Dictionary::Pair& pair : tags) { if (!MacroProcessor::ValidateMacroString(pair.second)) - BOOST_THROW_EXCEPTION(ValidationError(this, { "service_template", "tags", pair.first }, "Closing $ not found in macro format string '" + pair.second)); + BOOST_THROW_EXCEPTION(ValidationError(this, { "service_template", "tags", pair.first }, "Closing $ not found in macro format string '" + pair.second + "'.")); } } } From 90c76ad89cee2577fa8b038bcc3cb01f343ca763 Mon Sep 17 00:00:00 2001 From: Sebastian Grund Date: Thu, 26 Sep 2024 10:04:02 +0200 Subject: [PATCH 070/415] Update Authors --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 3253f3ec3..91aa37382 100644 --- a/AUTHORS +++ b/AUTHORS @@ -255,6 +255,7 @@ Sascha Westermann Sebastian Brückner Sebastian Chrostek Sebastian Eikenberg +Sebastian Grund Sebastian Marsching Silas <67681686+Tqnsls@users.noreply.github.com> Simon Murray From 2bbeaec916b70c65676db142151eafa3c5b01282 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 27 Sep 2024 12:38:18 +0200 Subject: [PATCH 071/415] Fix build on Mac with -DICINGA2_UNITY_BUILD=OFF -DICINGA2_WITH_LIVESTATUS=ON error: no matching function for call to 'intrusive_ptr_release' ... candidate function not viable: cannot convert argument of incomplete type 'icinga::Notification *' to 'Object *' for 1st argument void intrusive_ptr_release(Object *object); --- lib/icinga/notification.hpp | 3 ++- lib/icinga/usergroup.hpp | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/icinga/notification.hpp b/lib/icinga/notification.hpp index 1b6cbedb1..8c5a5f4b1 100644 --- a/lib/icinga/notification.hpp +++ b/lib/icinga/notification.hpp @@ -55,6 +55,7 @@ class ApplyRule; struct ScriptFrame; class Host; class Service; +class UserGroup; /** * An Icinga notification specification. @@ -73,7 +74,7 @@ public: intrusive_ptr GetCommand() const; TimePeriod::Ptr GetPeriod() const; std::set GetUsers() const; - std::set GetUserGroups() const; + std::set> GetUserGroups() const; void UpdateNotificationNumber(); void ResetNotificationNumber(); diff --git a/lib/icinga/usergroup.hpp b/lib/icinga/usergroup.hpp index c6f82a131..3435f6c16 100644 --- a/lib/icinga/usergroup.hpp +++ b/lib/icinga/usergroup.hpp @@ -4,6 +4,7 @@ #define USERGROUP_H #include "icinga/i2-icinga.hpp" +#include "icinga/notification.hpp" #include "icinga/usergroup-ti.hpp" #include "icinga/user.hpp" From f0e084d5307a1e8ac1867471ba69c341f0355b91 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Fri, 27 Sep 2024 14:23:05 +0200 Subject: [PATCH 072/415] Log: fix some parts of messages not being discarded early `m_IsNoOp` was introduced to avoid building up log messages that will later be discarded, like debug messages if no debug logging is configured. However, it looks like the template operator<< implemented in the header file was forgotten when adding this feature, all other places writing into `m_Buffer` already have an if guard like added by this commit. --- lib/base/logger.hpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/base/logger.hpp b/lib/base/logger.hpp index 10e0872ae..7b4758d8b 100644 --- a/lib/base/logger.hpp +++ b/lib/base/logger.hpp @@ -121,7 +121,10 @@ public: template Log& operator<<(const T& val) { - m_Buffer << val; + if (!m_IsNoOp) { + m_Buffer << val; + } + return *this; } From dc4869c3aac165fa433ee57410ef79ffda28e757 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 14 May 2024 11:42:26 +0200 Subject: [PATCH 073/415] IcingaDB::TimestampToMilliseconds(): limit output to four year digits Too high timestamps may overflow uint64_t (and the YYYY format) and negative ones don't fit into uint64_t. Those may crash our Go daemon. --- lib/icingadb/icingadb-utility.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/icingadb/icingadb-utility.cpp b/lib/icingadb/icingadb-utility.cpp index b247ed84a..35f503ab5 100644 --- a/lib/icingadb/icingadb-utility.cpp +++ b/lib/icingadb/icingadb-utility.cpp @@ -18,6 +18,7 @@ #include "icinga/eventcommand.hpp" #include "icinga/host.hpp" #include +#include #include #include #include @@ -245,7 +246,18 @@ String IcingaDB::GetLowerCaseTypeNameDB(const ConfigObject::Ptr& obj) } long long IcingaDB::TimestampToMilliseconds(double timestamp) { - return static_cast(timestamp * 1000); + // In addition to the limits of the Icinga DB MySQL (0 - 2^64) and PostgreSQL (0 - 2^63) schemata, + // years not fitting in YYYY may cause problems, see e.g. https://github.com/golang/go/issues/4556. + // RFC 3339: "All dates and times are assumed to be (...) somewhere between 0000AD and 9999AD." + // + // The below upper limit includes a safety buffer to make sure the timestamp is within 9999AD in all time zones: + // $ date -ud @253402214400 + // Fri Dec 31 00:00:00 UTC 9999 + // $ TZ=Asia/Vladivostok date -d @253402214400 + // Fri Dec 31 10:00:00 +10 9999 + // $ TZ=America/Juneau date -d @253402214400 + // Thu Dec 30 15:00:00 AKST 9999 + return std::fmin(std::fmax(timestamp, 0.0), 253402214400.0) * 1000.0; } String IcingaDB::IcingaToStreamValue(const Value& value) From ad6fcda6dfed8bf2475988bfdcbd5f9320a2a83d Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 13 May 2024 16:54:38 +0200 Subject: [PATCH 074/415] Ido*sqlConnection#FieldToEscapedString(): don't overflow timestamps > long --- lib/db_ido_mysql/idomysqlconnection.cpp | 4 ++-- lib/db_ido_pgsql/idopgsqlconnection.cpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/db_ido_mysql/idomysqlconnection.cpp b/lib/db_ido_mysql/idomysqlconnection.cpp index aacb7d7bd..74cb1d712 100644 --- a/lib/db_ido_mysql/idomysqlconnection.cpp +++ b/lib/db_ido_mysql/idomysqlconnection.cpp @@ -891,9 +891,9 @@ bool IdoMysqlConnection::FieldToEscapedString(const String& key, const Value& va *result = static_cast(dbrefcol); } else if (DbValue::IsTimestamp(value)) { - long ts = rawvalue; + double ts = rawvalue; std::ostringstream msgbuf; - msgbuf << "FROM_UNIXTIME(" << ts << ")"; + msgbuf << "FROM_UNIXTIME(" << std::fixed << std::setprecision(0) << ts << ")"; *result = Value(msgbuf.str()); } else if (DbValue::IsObjectInsertID(value)) { auto id = static_cast(rawvalue); diff --git a/lib/db_ido_pgsql/idopgsqlconnection.cpp b/lib/db_ido_pgsql/idopgsqlconnection.cpp index 07e88e6bb..db5629b82 100644 --- a/lib/db_ido_pgsql/idopgsqlconnection.cpp +++ b/lib/db_ido_pgsql/idopgsqlconnection.cpp @@ -700,9 +700,9 @@ bool IdoPgsqlConnection::FieldToEscapedString(const String& key, const Value& va *result = static_cast(dbrefcol); } else if (DbValue::IsTimestamp(value)) { - long ts = rawvalue; + double ts = rawvalue; std::ostringstream msgbuf; - msgbuf << "TO_TIMESTAMP(" << ts << ") AT TIME ZONE 'UTC'"; + msgbuf << "TO_TIMESTAMP(" << std::fixed << std::setprecision(0) << ts << ") AT TIME ZONE 'UTC'"; *result = Value(msgbuf.str()); } else if (DbValue::IsObjectInsertID(value)) { auto id = static_cast(rawvalue); From c6f9de5933bb0866bda3b942bd10c4078b1569e7 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 5 Jun 2024 15:14:00 +0200 Subject: [PATCH 075/415] Ido*sqlConnection#FieldToEscapedString(): don't write out of range time MySQL's FROM_UNIXTIME() NULLs ts <1970, errors for >2038. Postgres' TO_TIMESTAMP() errors for all ts not between 4713BC - 294276AD. --- lib/db_ido_mysql/idomysqlconnection.cpp | 6 +++- lib/db_ido_pgsql/idopgsqlconnection.cpp | 43 ++++++++++++++++++++++++- 2 files changed, 47 insertions(+), 2 deletions(-) diff --git a/lib/db_ido_mysql/idomysqlconnection.cpp b/lib/db_ido_mysql/idomysqlconnection.cpp index 74cb1d712..7af82e5fa 100644 --- a/lib/db_ido_mysql/idomysqlconnection.cpp +++ b/lib/db_ido_mysql/idomysqlconnection.cpp @@ -891,9 +891,13 @@ bool IdoMysqlConnection::FieldToEscapedString(const String& key, const Value& va *result = static_cast(dbrefcol); } else if (DbValue::IsTimestamp(value)) { + // MySQL TIMESTAMP columns have the year-2038 problem, hence the upper limit below. + // Also, they don't accept FROM_UNIXTIME(0): ERROR 1292 (22007): Incorrect datetime value: '1970-01-01 00:00:00' + double ts = rawvalue; std::ostringstream msgbuf; - msgbuf << "FROM_UNIXTIME(" << std::fixed << std::setprecision(0) << ts << ")"; + msgbuf << "FROM_UNIXTIME(" << std::fixed << std::setprecision(0) + << std::fmin(std::fmax(ts, 1.0), 2147483647.0) << ")"; *result = Value(msgbuf.str()); } else if (DbValue::IsObjectInsertID(value)) { auto id = static_cast(rawvalue); diff --git a/lib/db_ido_pgsql/idopgsqlconnection.cpp b/lib/db_ido_pgsql/idopgsqlconnection.cpp index db5629b82..a9c622afd 100644 --- a/lib/db_ido_pgsql/idopgsqlconnection.cpp +++ b/lib/db_ido_pgsql/idopgsqlconnection.cpp @@ -15,6 +15,7 @@ #include "base/context.hpp" #include "base/statsfunction.hpp" #include "base/defer.hpp" +#include #include using namespace icinga; @@ -700,9 +701,49 @@ bool IdoPgsqlConnection::FieldToEscapedString(const String& key, const Value& va *result = static_cast(dbrefcol); } else if (DbValue::IsTimestamp(value)) { + // In addition to the limits of PostgreSQL itself (4713BC - 294276AD), + // years not fitting in YYYY may cause problems, see e.g. https://github.com/golang/go/issues/4556. + // RFC 3339: "All dates and times are assumed to be (...) somewhere between 0000AD and 9999AD." + // The below limits include safety buffers to make sure the timestamps are within 0-9999 AD in all time zones: + // + // postgres=# \x + // Expanded display is on. + // postgres=# SELECT TO_TIMESTAMP(-62135510400) AT TIME ZONE 'UTC' AS utc, + // postgres-# TO_TIMESTAMP(-62135510400) AT TIME ZONE 'Asia/Vladivostok' AS east, + // postgres-# TO_TIMESTAMP(-62135510400) AT TIME ZONE 'America/Juneau' AS west, + // postgres-# TO_TIMESTAMP(-62135510400) AT TIME ZONE 'America/Nuuk' AS north; + // -[ RECORD 1 ]-------------- + // utc | 0001-01-02 00:00:00 + // east | 0001-01-02 08:47:31 + // west | 0001-01-02 15:02:19 + // north | 0001-01-01 20:33:04 + // + // postgres=# SELECT TO_TIMESTAMP(-62135510400-86400) AT TIME ZONE 'UTC' AS utc, + // postgres-# TO_TIMESTAMP(-62135510400-86400) AT TIME ZONE 'Asia/Vladivostok' AS east, + // postgres-# TO_TIMESTAMP(-62135510400-86400) AT TIME ZONE 'America/Juneau' AS west, + // postgres-# TO_TIMESTAMP(-62135510400-86400) AT TIME ZONE 'America/Nuuk' AS north; + // -[ RECORD 1 ]----------------- + // utc | 0001-01-01 00:00:00 + // east | 0001-01-01 08:47:31 + // west | 0001-01-01 15:02:19 + // north | 0001-12-31 20:33:04 BC + // + // postgres=# SELECT TO_TIMESTAMP(253402214400) AT TIME ZONE 'UTC' AS utc, + // postgres-# TO_TIMESTAMP(253402214400) AT TIME ZONE 'Asia/Vladivostok' AS east, + // postgres-# TO_TIMESTAMP(253402214400) AT TIME ZONE 'America/Juneau' AS west, + // postgres-# TO_TIMESTAMP(253402214400) AT TIME ZONE 'America/Nuuk' AS north; + // -[ RECORD 1 ]------------- + // utc | 9999-12-31 00:00:00 + // east | 9999-12-31 10:00:00 + // west | 9999-12-30 15:00:00 + // north | 9999-12-30 22:00:00 + // + // postgres=# + double ts = rawvalue; std::ostringstream msgbuf; - msgbuf << "TO_TIMESTAMP(" << std::fixed << std::setprecision(0) << ts << ") AT TIME ZONE 'UTC'"; + msgbuf << "TO_TIMESTAMP(" << std::fixed << std::setprecision(0) + << std::fmin(std::fmax(ts, -62135510400.0), 253402214400.0) << ") AT TIME ZONE 'UTC'"; *result = Value(msgbuf.str()); } else if (DbValue::IsObjectInsertID(value)) { auto id = static_cast(rawvalue); From 7d0a43f926828dea76064cd1f8530eb2155048eb Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Fri, 11 Oct 2024 12:59:04 +0200 Subject: [PATCH 076/415] Use `Checkable::GetStateBeforeSuppression()` only where relevant This fixes an issue where recovery notifications get lost if they happen outside of a notification time period. Not all calls to `Checkable::NotificationReasonApplies()` need `GetStateBeforeSuppression()` to be checked. In fact, for one caller, `FireSuppressedNotifications()` in `lib/notification/notificationcomponent.cpp`, the state before suppression may not even be initialized properly, so that the default value of OK is used which can lead to incorrect return values. Note the difference between suppressions happening on the level of the `Checkable` object level and the `Notification` object level. Only the first sets the state before suppression in the `Checkable` object, but so far, also the latter used that value incorrectly. This commit moves the check of `GetStateBeforeSuppression()` from `Checkable::NotificationReasonApplies()` to the one place where it's actually relevant: `Checkable::FireSuppressedNotifications()`. This made the existing call to `NotificationReasonApplies()` unneccessary as it would always return true: the `type` argument is computed based on the current check result, so there's no need to check it against the current check result. --- lib/icinga/checkable-notification.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/icinga/checkable-notification.cpp b/lib/icinga/checkable-notification.cpp index 79b598612..2a1150556 100644 --- a/lib/icinga/checkable-notification.cpp +++ b/lib/icinga/checkable-notification.cpp @@ -203,7 +203,7 @@ void Checkable::FireSuppressedNotifications() * If any of these conditions is not met, processing the suppressed notification is further delayed. */ if (!state_suppressed && GetStateType() == StateTypeHard && !IsLikelyToBeCheckedSoon() && !wasLastParentRecoveryRecent.Get()) { - if (NotificationReasonApplies(type)) { + if (cr->GetState() != GetStateBeforeSuppression()) { Checkable::OnNotificationsRequested(this, type, cr, "", "", nullptr); } subtract |= NotificationRecovery|NotificationProblem; @@ -266,12 +266,12 @@ bool Checkable::NotificationReasonApplies(NotificationType type) case NotificationProblem: { auto cr (GetLastCheckResult()); - return cr && !IsStateOK(cr->GetState()) && cr->GetState() != GetStateBeforeSuppression(); + return cr && !IsStateOK(cr->GetState()); } case NotificationRecovery: { auto cr (GetLastCheckResult()); - return cr && IsStateOK(cr->GetState()) && cr->GetState() != GetStateBeforeSuppression(); + return cr && IsStateOK(cr->GetState()); } case NotificationFlappingStart: return IsFlapping(); From 39337fbeae9319f4dbbcb36757498486bd7d879e Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 21 Oct 2024 12:35:09 +0200 Subject: [PATCH 077/415] docs: Add `$` to the escape sequences section feat: Add the `$` character to the escape sequences table. --- doc/17-language-reference.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/doc/17-language-reference.md b/doc/17-language-reference.md index 5686d558a..53bea28a3 100644 --- a/doc/17-language-reference.md +++ b/doc/17-language-reference.md @@ -97,6 +97,7 @@ Character | Escape sequence --------------------------|------------------------------------ " | \\" \\ | \\\\ +$ | $$ <TAB> | \\t <CARRIAGE-RETURN> | \\r <LINE-FEED> | \\n @@ -107,6 +108,10 @@ In addition to these pre-defined escape sequences you can specify arbitrary ASCII characters using the backslash character (\\) followed by an ASCII character in octal encoding. +In Icinga 2, the `$` character is reserved for resolving [runtime macros](03-monitoring-basics.md#runtime-macros). +However, in situations where a string that isn't intended to be used as a runtime macro contains the `$` character, +it is necessary to escape it with another `$` character. + ### Multi-line String Literals Strings spanning multiple lines can be specified by enclosing them in From 9fa438c956656fd361725934b2d6f1b119af04f1 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 21 Oct 2024 12:38:53 +0200 Subject: [PATCH 078/415] docs: Add missing space --- doc/14-features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/14-features.md b/doc/14-features.md index 4a6e1ce48..6ad4d1095 100644 --- a/doc/14-features.md +++ b/doc/14-features.md @@ -106,7 +106,7 @@ The current naming schema is defined as follows. The [Icinga Web 2 Graphite modu depends on this schema. The default prefix for hosts and services is configured using -[runtime macros](03-monitoring-basics.md#runtime-macros)like this: +[runtime macros](03-monitoring-basics.md#runtime-macros) like this: ``` icinga2.$host.name$.host.$host.check_command$ From 869a7d6f0fe38c748e67bacc1fbdd42c933030f6 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Wed, 16 Oct 2024 12:00:00 +0200 Subject: [PATCH 079/415] Security: fix TLS certificate validation bypass The previous validation in set_verify_callback() could be bypassed, tricking Icinga 2 into treating invalid certificates as valid. To fix this, the validation checks were moved into the IsVerifyOK() function. This is tracked as CVE-2024-49369, more details will be published at a later time. --- lib/base/tlsstream.cpp | 62 ++++++++++++++++++++++++++++++++---------- lib/base/tlsstream.hpp | 8 ++---- 2 files changed, 51 insertions(+), 19 deletions(-) diff --git a/lib/base/tlsstream.cpp b/lib/base/tlsstream.cpp index db54c919e..a71451a53 100644 --- a/lib/base/tlsstream.cpp +++ b/lib/base/tlsstream.cpp @@ -18,14 +18,48 @@ using namespace icinga; -bool UnbufferedAsioTlsStream::IsVerifyOK() const +/** + * Checks whether the TLS handshake was completed with a valid peer certificate. + * + * @return true if the peer presented a valid certificate, false otherwise + */ +bool UnbufferedAsioTlsStream::IsVerifyOK() { - return m_VerifyOK; + if (!SSL_is_init_finished(native_handle())) { + // handshake was not completed + return false; + } + + if (GetPeerCertificate() == nullptr) { + // no peer certificate was sent + return false; + } + + return SSL_get_verify_result(native_handle()) == X509_V_OK; } -String UnbufferedAsioTlsStream::GetVerifyError() const +/** + * Returns a human-readable error string for situations where IsVerifyOK() returns false. + * + * If the handshake was completed and a peer certificate was provided, + * the string additionally contains the OpenSSL verification error code. + * + * @return string containing the error message + */ +String UnbufferedAsioTlsStream::GetVerifyError() { - return m_VerifyError; + if (!SSL_is_init_finished(native_handle())) { + return "handshake not completed"; + } + + if (GetPeerCertificate() == nullptr) { + return "no peer certificate provided"; + } + + std::ostringstream buf; + long err = SSL_get_verify_result(native_handle()); + buf << "code " << err << ": " << X509_verify_cert_error_string(err); + return buf.str(); } std::shared_ptr UnbufferedAsioTlsStream::GetPeerCertificate() @@ -43,17 +77,17 @@ void UnbufferedAsioTlsStream::BeforeHandshake(handshake_type type) set_verify_mode(ssl::verify_peer | ssl::verify_client_once); - set_verify_callback([this](bool preverified, ssl::verify_context& ctx) { - if (!preverified) { - m_VerifyOK = false; - - std::ostringstream msgbuf; - int err = X509_STORE_CTX_get_error(ctx.native_handle()); - - msgbuf << "code " << err << ": " << X509_verify_cert_error_string(err); - m_VerifyError = msgbuf.str(); - } + set_verify_callback([](bool preverified, ssl::verify_context& ctx) { + (void) preverified; + (void) ctx; + /* Continue the handshake even if an invalid peer certificate was presented. The verification result has to be + * checked using the IsVerifyOK() method. + * + * Such connections are used for the initial enrollment of nodes where they use a self-signed certificate to + * send a certificate request and receive their valid certificate after approval (manually by the administrator + * or using a certificate ticket). + */ return true; }); diff --git a/lib/base/tlsstream.hpp b/lib/base/tlsstream.hpp index f6e52097e..9a6340baf 100644 --- a/lib/base/tlsstream.hpp +++ b/lib/base/tlsstream.hpp @@ -70,12 +70,12 @@ class UnbufferedAsioTlsStream : public AsioTcpTlsStream public: inline UnbufferedAsioTlsStream(UnbufferedAsioTlsStreamParams& init) - : AsioTcpTlsStream(init.IoContext, init.SslContext), m_VerifyOK(true), m_Hostname(init.Hostname) + : AsioTcpTlsStream(init.IoContext, init.SslContext), m_Hostname(init.Hostname) { } - bool IsVerifyOK() const; - String GetVerifyError() const; + bool IsVerifyOK(); + String GetVerifyError(); std::shared_ptr GetPeerCertificate(); template @@ -97,8 +97,6 @@ public: } private: - bool m_VerifyOK; - String m_VerifyError; String m_Hostname; void BeforeHandshake(handshake_type type); From b95858d4d19780c844abe61d0975b5ca4d6460ae Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 23 Oct 2024 10:33:23 +0200 Subject: [PATCH 080/415] GHA: Windows: don't require git.icinga.com/packaging/windows-icinga2 "A little copying is better than a little dependency." - https://www.youtube.com/watch?v=PAAkCSZUG1c&t=9m28s (Gopherfest 2015 | Go Proverbs with Rob Pike) --- .github/workflows/windows.yml | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 18d8e5757..5d682be3d 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -25,29 +25,24 @@ jobs: env: BITS: '${{ matrix.bits }}' - ICINGA_BUILD_TYPE: snapshot - UPSTREAM_GIT_URL: file://D:/a/icinga2/icinga2/.git + CMAKE_BUILD_TYPE: RelWithDebInfo steps: - name: Checkout HEAD uses: actions/checkout@v1 - - name: windows-icinga2 - run: | - git clone https://git.icinga.com/packaging/windows-icinga2.git - - name: Build tools run: | + Set-PSDebug -Trace 1 & .\doc\win-dev.ps1 - - name: Source - run: | - git checkout -B master - cd windows-icinga2 - & .\source.ps1 - - name: Binary - working-directory: windows-icinga2 run: | - New-Item -ItemType Directory -Path 'C:\Program Files\Icinga2\WillBeRemoved' -ErrorAction SilentlyContinue - & .\build.ps1 + Set-PSDebug -Trace 1 + & .\tools\win32\load-vsenv.ps1 + & powershell.exe .\tools\win32\configure.ps1 + if ($LastExitCode -ne 0) { throw "Error during configure" } + & powershell.exe .\tools\win32\build.ps1 + if ($LastExitCode -ne 0) { throw "Error during build" } + & powershell.exe .\tools\win32\test.ps1 + if ($LastExitCode -ne 0) { throw "Error during test" } From 7a4ba59961041f6bea4a1b3baa2d93c6184013c0 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 23 Oct 2024 13:06:12 +0200 Subject: [PATCH 081/415] Remove redundant "Validation failed" prefix from ValidationError exceptions ValidationError#ValidationError() already prefixes #m_What, which #what() returns, with "Validation failed for object". --- lib/icinga/command.cpp | 2 +- lib/icinga/notification.cpp | 2 +- lib/icingadb/icingadb.cpp | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/icinga/command.cpp b/lib/icinga/command.cpp index 8e0f357e7..ef6676b05 100644 --- a/lib/icinga/command.cpp +++ b/lib/icinga/command.cpp @@ -35,7 +35,7 @@ void Command::Validate(int types, const ValidationUtils& utils) Value argvalue = argdict->Get("value"); if (argvalue.IsString() && !MacroProcessor::ValidateMacroString(argvalue)) - BOOST_THROW_EXCEPTION(ValidationError(this, { "arguments", kv.first, "value" }, "Validation failed: Closing $ not found in macro format string '" + argvalue + "'.")); + BOOST_THROW_EXCEPTION(ValidationError(this, { "arguments", kv.first, "value" }, "Closing $ not found in macro format string '" + argvalue + "'.")); } if (argdict->Contains("set_if")) { diff --git a/lib/icinga/notification.cpp b/lib/icinga/notification.cpp index ab8d42b8c..81a48bada 100644 --- a/lib/icinga/notification.cpp +++ b/lib/icinga/notification.cpp @@ -739,7 +739,7 @@ void Notification::Validate(int types, const ValidationUtils& utils) Array::Ptr groups = GetUserGroupsRaw(); if ((!users || users->GetLength() == 0) && (!groups || groups->GetLength() == 0)) - BOOST_THROW_EXCEPTION(ValidationError(this, std::vector(), "Validation failed: No users/user_groups specified.")); + BOOST_THROW_EXCEPTION(ValidationError(this, std::vector(), "No users/user_groups specified.")); } void Notification::ValidateStates(const Lazy& lvalue, const ValidationUtils& utils) diff --git a/lib/icingadb/icingadb.cpp b/lib/icingadb/icingadb.cpp index 6d5ded9cf..b9b440939 100644 --- a/lib/icingadb/icingadb.cpp +++ b/lib/icingadb/icingadb.cpp @@ -48,14 +48,13 @@ void IcingaDB::Validate(int types, const ValidationUtils& utils) return; if (GetEnableTls() && GetCertPath().IsEmpty() != GetKeyPath().IsEmpty()) { - BOOST_THROW_EXCEPTION(ValidationError(this, std::vector(), "Validation failed: Either both a client certificate (cert_path) and its private key (key_path) or none of them must be given.")); + BOOST_THROW_EXCEPTION(ValidationError(this, std::vector(), "Either both a client certificate (cert_path) and its private key (key_path) or none of them must be given.")); } try { InitEnvironmentId(); } catch (const std::exception& e) { - BOOST_THROW_EXCEPTION(ValidationError(this, std::vector(), - String("Validation failed: ") + e.what())); + BOOST_THROW_EXCEPTION(ValidationError(this, std::vector(), e.what())); } } From 8db62744cf098d4edba397ef93ed7543bc1ee5af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Wed, 23 Oct 2024 14:42:15 +0200 Subject: [PATCH 082/415] GHA: Linux: include Ubuntu 24.10 --- .github/workflows/linux.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 19f87097d..68878fdfe 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -40,6 +40,7 @@ jobs: - ubuntu:23.04 - ubuntu:23.10 - ubuntu:24.04 + - ubuntu:24.10 steps: - name: Checkout HEAD From 57fab7f39e4751222340675a118f1193e32ff778 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Thu, 19 Sep 2024 16:03:55 +0200 Subject: [PATCH 083/415] Icinga DB: Config no_user_modify Each configuration field of an IcingaDB Object was marked with no_user_modify as modifications via the API would not result in an actual change. While the Object would be updated, the internal Redis connection would not be restarted, resulting in an unexpected behavior. The missing db_index was added to the documentation. --- doc/09-object-types.md | 1 + lib/icingadb/icingadb.ti | 26 +++++++++++++------------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/doc/09-object-types.md b/doc/09-object-types.md index 6b5d355ab..44c52da13 100644 --- a/doc/09-object-types.md +++ b/doc/09-object-types.md @@ -1388,6 +1388,7 @@ Configuration Attributes: port | Number | **Optional.** Redis port. Defaults to `6380` since the Redis server provided by the `icingadb-redis` package listens on that port. path | String | **Optional.** Redis unix socket path. Can be used instead of `host` and `port` attributes. password | String | **Optional.** Redis auth password. + db\_index | Number | **Optional.** Redis logical database by its number. Defaults to `0`. enable\_tls | Boolean | **Optional.** Whether to use TLS. cert\_path | String | **Optional.** Path to the certificate. key\_path | String | **Optional.** Path to the private key. diff --git a/lib/icingadb/icingadb.ti b/lib/icingadb/icingadb.ti index 1c649c8e4..2cc54ccc1 100644 --- a/lib/icingadb/icingadb.ti +++ b/lib/icingadb/icingadb.ti @@ -12,36 +12,36 @@ class IcingaDB : ConfigObject { activation_priority 100; - [config] String host { + [config, no_user_modify] String host { default {{{ return "127.0.0.1"; }}} }; - [config] int port { + [config, no_user_modify] int port { default {{{ return 6380; }}} }; - [config] String path; + [config, no_user_modify] String path; [config, no_user_view, no_user_modify] String password; - [config] int db_index; + [config, no_user_modify] int db_index; - [config] bool enable_tls { + [config, no_user_modify] bool enable_tls { default {{{ return false; }}} }; - [config] bool insecure_noverify { + [config, no_user_modify] bool insecure_noverify { default {{{ return false; }}} }; - [config] String cert_path; - [config] String key_path; - [config] String ca_path; - [config] String crl_path; - [config] String cipher_list { + [config, no_user_modify] String cert_path; + [config, no_user_modify] String key_path; + [config, no_user_modify] String ca_path; + [config, no_user_modify] String crl_path; + [config, no_user_modify] String cipher_list { default {{{ return DEFAULT_TLS_CIPHERS; }}} }; - [config] String tls_protocolmin { + [config, no_user_modify] String tls_protocolmin { default {{{ return DEFAULT_TLS_PROTOCOLMIN; }}} }; - [config] double connect_timeout { + [config, no_user_modify] double connect_timeout { default {{{ return DEFAULT_CONNECT_TIMEOUT; }}} }; From 98f60fd78ebee9a657eef035e03d6b7a1fd921b8 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Thu, 19 Sep 2024 16:10:07 +0200 Subject: [PATCH 084/415] Icinga DB: Support Redis username authentication The Redis ACL system was introduced with Redis 6.0. It introduced users with precisely granular permissions. This change allows Icinga 2 to use the Icinga DB feature against a Redis with an ACL user. This was reflected in the documentation, next to the already implemented, but undocumented Redis database. Closes #9536. --- doc/09-object-types.md | 1 + lib/icingadb/icingadb.cpp | 9 +++++++-- lib/icingadb/icingadb.ti | 1 + lib/icingadb/redisconnection.cpp | 8 ++++---- lib/icingadb/redisconnection.hpp | 9 ++++++--- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/doc/09-object-types.md b/doc/09-object-types.md index 44c52da13..c2d5e5eff 100644 --- a/doc/09-object-types.md +++ b/doc/09-object-types.md @@ -1387,6 +1387,7 @@ Configuration Attributes: host | String | **Optional.** Redis host. Defaults to `127.0.0.1`. port | Number | **Optional.** Redis port. Defaults to `6380` since the Redis server provided by the `icingadb-redis` package listens on that port. path | String | **Optional.** Redis unix socket path. Can be used instead of `host` and `port` attributes. + username | String | **Optional.** Redis auth username. Only possible if Redis ACLs are used. Requires `password` to be set as well. password | String | **Optional.** Redis auth password. db\_index | Number | **Optional.** Redis logical database by its number. Defaults to `0`. enable\_tls | Boolean | **Optional.** Whether to use TLS. diff --git a/lib/icingadb/icingadb.cpp b/lib/icingadb/icingadb.cpp index 6d5ded9cf..80e297e4a 100644 --- a/lib/icingadb/icingadb.cpp +++ b/lib/icingadb/icingadb.cpp @@ -47,6 +47,11 @@ void IcingaDB::Validate(int types, const ValidationUtils& utils) if (!(types & FAConfig)) return; + if (!GetUsername().IsEmpty() && GetPassword().IsEmpty()) { + BOOST_THROW_EXCEPTION(ValidationError(this, std::vector(), + "Redis password must be set, if username is provided.")); + } + if (GetEnableTls() && GetCertPath().IsEmpty() != GetKeyPath().IsEmpty()) { BOOST_THROW_EXCEPTION(ValidationError(this, std::vector(), "Validation failed: Either both a client certificate (cert_path) and its private key (key_path) or none of them must be given.")); } @@ -77,7 +82,7 @@ void IcingaDB::Start(bool runtimeCreated) m_WorkQueue.SetExceptionCallback([this](boost::exception_ptr exp) { ExceptionHandler(std::move(exp)); }); - m_Rcon = new RedisConnection(GetHost(), GetPort(), GetPath(), GetPassword(), GetDbIndex(), + m_Rcon = new RedisConnection(GetHost(), GetPort(), GetPath(), GetUsername(), GetPassword(), GetDbIndex(), GetEnableTls(), GetInsecureNoverify(), GetCertPath(), GetKeyPath(), GetCaPath(), GetCrlPath(), GetTlsProtocolmin(), GetCipherList(), GetConnectTimeout(), GetDebugInfo()); m_RconLocked.store(m_Rcon); @@ -87,7 +92,7 @@ void IcingaDB::Start(bool runtimeCreated) if (!ctype) continue; - RedisConnection::Ptr con = new RedisConnection(GetHost(), GetPort(), GetPath(), GetPassword(), GetDbIndex(), + RedisConnection::Ptr con = new RedisConnection(GetHost(), GetPort(), GetPath(), GetUsername(), GetPassword(), GetDbIndex(), GetEnableTls(), GetInsecureNoverify(), GetCertPath(), GetKeyPath(), GetCaPath(), GetCrlPath(), GetTlsProtocolmin(), GetCipherList(), GetConnectTimeout(), GetDebugInfo(), m_Rcon); diff --git a/lib/icingadb/icingadb.ti b/lib/icingadb/icingadb.ti index 2cc54ccc1..c4037b14d 100644 --- a/lib/icingadb/icingadb.ti +++ b/lib/icingadb/icingadb.ti @@ -19,6 +19,7 @@ class IcingaDB : ConfigObject default {{{ return 6380; }}} }; [config, no_user_modify] String path; + [config, no_user_modify] String username; [config, no_user_view, no_user_modify] String password; [config, no_user_modify] int db_index; diff --git a/lib/icingadb/redisconnection.cpp b/lib/icingadb/redisconnection.cpp index 798a8279b..c187d7f1e 100644 --- a/lib/icingadb/redisconnection.cpp +++ b/lib/icingadb/redisconnection.cpp @@ -30,18 +30,18 @@ namespace asio = boost::asio; boost::regex RedisConnection::m_ErrAuth ("\\AERR AUTH "); -RedisConnection::RedisConnection(const String& host, int port, const String& path, const String& password, int db, +RedisConnection::RedisConnection(const String& host, int port, const String& path, const String& username, const String& password, int db, bool useTls, bool insecure, const String& certPath, const String& keyPath, const String& caPath, const String& crlPath, const String& tlsProtocolmin, const String& cipherList, double connectTimeout, DebugInfo di, const RedisConnection::Ptr& parent) - : RedisConnection(IoEngine::Get().GetIoContext(), host, port, path, password, db, + : RedisConnection(IoEngine::Get().GetIoContext(), host, port, path, username, password, db, useTls, insecure, certPath, keyPath, caPath, crlPath, tlsProtocolmin, cipherList, connectTimeout, std::move(di), parent) { } -RedisConnection::RedisConnection(boost::asio::io_context& io, String host, int port, String path, String password, +RedisConnection::RedisConnection(boost::asio::io_context& io, String host, int port, String path, String username, String password, int db, bool useTls, bool insecure, String certPath, String keyPath, String caPath, String crlPath, String tlsProtocolmin, String cipherList, double connectTimeout, DebugInfo di, const RedisConnection::Ptr& parent) - : m_Host(std::move(host)), m_Port(port), m_Path(std::move(path)), m_Password(std::move(password)), + : m_Host(std::move(host)), m_Port(port), m_Path(std::move(path)), m_Username(std::move(username)), m_Password(std::move(password)), m_DbIndex(db), m_CertPath(std::move(certPath)), m_KeyPath(std::move(keyPath)), m_Insecure(insecure), m_CaPath(std::move(caPath)), m_CrlPath(std::move(crlPath)), m_TlsProtocolmin(std::move(tlsProtocolmin)), m_CipherList(std::move(cipherList)), m_ConnectTimeout(connectTimeout), m_DebugInfo(std::move(di)), m_Connecting(false), m_Connected(false), diff --git a/lib/icingadb/redisconnection.hpp b/lib/icingadb/redisconnection.hpp index f346ba285..fecd236f9 100644 --- a/lib/icingadb/redisconnection.hpp +++ b/lib/icingadb/redisconnection.hpp @@ -84,7 +84,7 @@ namespace icinga : Config(config), State(state), History(history) { } }; - RedisConnection(const String& host, int port, const String& path, const String& password, int db, + RedisConnection(const String& host, int port, const String& path, const String& username, const String& password, int db, bool useTls, bool insecure, const String& certPath, const String& keyPath, const String& caPath, const String& crlPath, const String& tlsProtocolmin, const String& cipherList, double connectTimeout, DebugInfo di, const Ptr& parent = nullptr); @@ -196,7 +196,7 @@ namespace icinga static boost::regex m_ErrAuth; - RedisConnection(boost::asio::io_context& io, String host, int port, String path, String password, + RedisConnection(boost::asio::io_context& io, String host, int port, String path, String username, String password, int db, bool useTls, bool insecure, String certPath, String keyPath, String caPath, String crlPath, String tlsProtocolmin, String cipherList, double connectTimeout, DebugInfo di, const Ptr& parent); @@ -227,6 +227,7 @@ namespace icinga String m_Path; String m_Host; int m_Port; + String m_Username; String m_Password; int m_DbIndex; @@ -457,7 +458,9 @@ void RedisConnection::Handshake(StreamPtr& strm, boost::asio::yield_context& yc) // Trigger NOAUTH WriteRESP(*strm, {"PING"}, yc); } else { - if (!m_Password.IsEmpty()) { + if (!m_Username.IsEmpty()) { + WriteRESP(*strm, {"AUTH", m_Username, m_Password}, yc); + } else if (!m_Password.IsEmpty()) { WriteRESP(*strm, {"AUTH", m_Password}, yc); } From 095e5982f45ac6fb972c6df348d5f8f3bfa34d98 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 24 Oct 2024 09:44:36 +0200 Subject: [PATCH 085/415] doc/: fix "a HA" -> "an HA" --- doc/06-distributed-monitoring.md | 2 +- doc/15-troubleshooting.md | 2 +- doc/19-technical-concepts.md | 2 +- lib/icinga/checkable-check.cpp | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/06-distributed-monitoring.md b/doc/06-distributed-monitoring.md index 1f24cb96f..b81f22b4b 100644 --- a/doc/06-distributed-monitoring.md +++ b/doc/06-distributed-monitoring.md @@ -3131,7 +3131,7 @@ object Endpoint "icinga2-master2.localdomain" { > **Note** > > This is required if you decide to change an already running single endpoint production -> environment into a HA-enabled cluster zone with two endpoints. +> environment into an HA-enabled cluster zone with two endpoints. > The [initial setup](06-distributed-monitoring.md#distributed-monitoring-scenarios-ha-master-clients) > with 2 HA masters doesn't require this step. diff --git a/doc/15-troubleshooting.md b/doc/15-troubleshooting.md index 4cc733854..e01c0d3f3 100644 --- a/doc/15-troubleshooting.md +++ b/doc/15-troubleshooting.md @@ -878,7 +878,7 @@ actively attempts to schedule and execute checks. Otherwise the node does not fe } ``` -You may ask why this analysis is important? Fair enough - if the numbers are not inverted in a HA zone +You may ask why this analysis is important? Fair enough - if the numbers are not inverted in an HA zone with two members, this may give a hint that the cluster nodes are in a split-brain scenario, or you've found a bug in the cluster. diff --git a/doc/19-technical-concepts.md b/doc/19-technical-concepts.md index 3f4b3812b..de68bdd63 100644 --- a/doc/19-technical-concepts.md +++ b/doc/19-technical-concepts.md @@ -651,7 +651,7 @@ authority = endpoints[Utility::SDBM(object->GetName()) % endpoints.size()] == my that by querying the `paused` attribute for all objects via REST API or debug console on both endpoints. -Endpoints inside a HA zone calculate the object authority independent from each other. +Endpoints inside an HA zone calculate the object authority independent from each other. This object authority is important for selected features explained below. Since features are configuration objects too, you must ensure that all nodes diff --git a/lib/icinga/checkable-check.cpp b/lib/icinga/checkable-check.cpp index f0af81bdd..6e3b8764b 100644 --- a/lib/icinga/checkable-check.cpp +++ b/lib/icinga/checkable-check.cpp @@ -363,7 +363,7 @@ Checkable::ProcessingResult Checkable::ProcessCheckResult(const CheckResult::Ptr // Don't recompute the next check when the current check isn't generated by this endpoint. When the check is // remotely generated we should've already received the "SetNextCheck" event before the "event::CheckResult" // cluster event. Otherwise, the next check received before this check will be invalidated and cause the Checkable - // "next_check/next_update" in a HA setup to always be different from the other endpoint as the "m_SchedulingOffset" + // "next_check/next_update" in an HA setup to always be different from the other endpoint as the "m_SchedulingOffset" // is randomly initialised on each node. if (!origin) { if (cr->GetActive()) { From e889528b14d7c883e3edd0bf82b9936929e521e2 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 8 May 2024 17:40:05 +0200 Subject: [PATCH 086/415] Document how to enable/disable Debug Output on the fly This is a good alternative to `icinga2 feature enable debuglog`: * Object creation/deletion via API happens immediately and requires no restart * Hence, the debug log is enabled exactly as long as desired Co-authored-by: alvar <8402811+oxzi@users.noreply.github.com> --- doc/15-troubleshooting.md | 58 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/doc/15-troubleshooting.md b/doc/15-troubleshooting.md index 4cc733854..fcf59d505 100644 --- a/doc/15-troubleshooting.md +++ b/doc/15-troubleshooting.md @@ -176,6 +176,64 @@ C:\> cd C:\ProgramData\icinga2\var\log\icinga2 C:\ProgramData\icinga2\var\log\icinga2> Get-Content .\debug.log -tail 10 -wait ``` +### Enable/Disable Debug Output on the fly + +The `debuglog` feature can also be created and deleted at runtime without having to restart Icinga 2. +Technically, this is possible because this feature is a [FileLogger](09-object-types.md#objecttype-filelogger) +that can be managed through the [API](12-icinga2-api.md#icinga2-api-config-objects). + +This is a good alternative to `icinga2 feature enable debuglog` as object +creation/deletion via API happens immediately and requires no restart. + +The above matters in setups large enough for the reload to take a while. +Especially these produce a lot of debug log output until disabled again. + +!!! info + + In case of [an HA zone](06-distributed-monitoring.md#distributed-monitoring-scenarios-ha-master-agents), + the following API examples toggle the feature on both nodes. + +#### Enable Debug Output on the fly + +```bash +curl -k -s -S -i -u root:icinga -H 'Accept: application/json' \ + -X PUT 'https://localhost:5665/v1/objects/fileloggers/on-the-fly-debug-file' \ + -d '{ "attrs": { "severity": "debug", "path": "/var/log/icinga2/on-the-fly-debug.log" }, "pretty": true }' +``` + +```json +{ + "results": [ + { + "code": 200.0, + "status": "Object was created." + } + ] +} +``` + +#### Disable Debug Output on the fly + +This works only for debug loggers enabled on the fly as above! + +```bash +curl -k -s -S -i -u root:icinga -H 'Accept: application/json' \ + -X DELETE 'https://localhost:5665/v1/objects/fileloggers/on-the-fly-debug-file?pretty=1' +``` + +```json +{ + "results": [ + { + "code": 200.0, + "name": "on-the-fly-debug-file", + "status": "Object was deleted.", + "type": "FileLogger" + } + ] +} +``` + ## Icinga starts/restarts/reloads very slowly ### Try swapping out the allocator From 73e992da81431ddcd3ad50ad0f732bad934da239 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Mon, 28 Oct 2024 12:12:35 +0100 Subject: [PATCH 087/415] openSUSE install docs: remove false info No packages to be installed according to these instructions require the given repo. --- doc/02-installation.md | 6 ------ 1 file changed, 6 deletions(-) diff --git a/doc/02-installation.md b/doc/02-installation.md index 8c78e6c55..35a7aef14 100644 --- a/doc/02-installation.md +++ b/doc/02-installation.md @@ -150,12 +150,6 @@ SUSEConnect -p PackageHub/$VERSION_ID/x86_64 zypper ar https://packages.icinga.com/openSUSE/ICINGA-release.repo zypper ref ``` - -You need to additionally add the `server:monitoring` repository to fulfill dependencies: - -```bash -zypper ar https://download.opensuse.org/repositories/server:/monitoring/15.3/server:monitoring.repo -``` From 9d4625e1ece2e02dd0158bfe2bcb5f7a5d8751b2 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 30 Oct 2024 11:26:21 +0100 Subject: [PATCH 088/415] ApiListener: Log connection attempts from an already connected client Something is definitely going wrong if a client tries to reconnect to this endpoint while it still has an active connection to that client. So we shouldn't hide this, but at least log it at info level. Apart from that, I've added some additional information about the currently active client, such as when the last message was sent and received. --- lib/remote/apilistener.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 7884131c4..cc9c3b5c8 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -843,9 +843,11 @@ void ApiListener::NewClientHandlerInternal( Log(LogNotice, "ApiListener", "New JSON-RPC client"); if (endpoint && endpoint->GetConnected()) { - Log(LogNotice, "ApiListener") + Log(LogInformation, "ApiListener") << "Ignoring JSON-RPC connection " << conninfo - << ". We're already connected to Endpoint '" << endpoint->GetName() << "'."; + << ". We're already connected to Endpoint '" << endpoint->GetName() + << "' (last message sent: " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S", endpoint->GetLastMessageSent()) + << ", last message received: " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S", endpoint->GetLastMessageReceived()) << ")."; return; } From e8b7baa298b033f54ab719d99a4e106b5f133a07 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 30 Oct 2024 14:31:48 +0100 Subject: [PATCH 089/415] JsonRpcConnection: Drop unused `m_NextHeartbeat` variable --- lib/remote/jsonrpcconnection.cpp | 2 +- lib/remote/jsonrpcconnection.hpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/remote/jsonrpcconnection.cpp b/lib/remote/jsonrpcconnection.cpp index 44f1662dd..7f05f4498 100644 --- a/lib/remote/jsonrpcconnection.cpp +++ b/lib/remote/jsonrpcconnection.cpp @@ -38,7 +38,7 @@ JsonRpcConnection::JsonRpcConnection(const String& identity, bool authenticated, JsonRpcConnection::JsonRpcConnection(const String& identity, bool authenticated, const Shared::Ptr& stream, ConnectionRole role, boost::asio::io_context& io) : m_Identity(identity), m_Authenticated(authenticated), m_Stream(stream), m_Role(role), - m_Timestamp(Utility::GetTime()), m_Seen(Utility::GetTime()), m_NextHeartbeat(0), m_IoStrand(io), + m_Timestamp(Utility::GetTime()), m_Seen(Utility::GetTime()), m_IoStrand(io), m_OutgoingMessagesQueued(io), m_WriterDone(io), m_ShuttingDown(false), m_CheckLivenessTimer(io), m_HeartbeatTimer(io) { diff --git a/lib/remote/jsonrpcconnection.hpp b/lib/remote/jsonrpcconnection.hpp index 3515573bb..bbe8588a1 100644 --- a/lib/remote/jsonrpcconnection.hpp +++ b/lib/remote/jsonrpcconnection.hpp @@ -73,7 +73,6 @@ private: ConnectionRole m_Role; double m_Timestamp; double m_Seen; - double m_NextHeartbeat; boost::asio::io_context::strand m_IoStrand; std::vector m_OutgoingMessagesQueue; AsioConditionVariable m_OutgoingMessagesQueued; From 857435744347b0c02db3bc424fe6ef198d706528 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 30 Oct 2024 16:55:13 +0100 Subject: [PATCH 090/415] ApiListener: Log error context only once When logging at the warning level, the logger will automatically look up for registered context and append them to the log entry accordingly. --- lib/remote/apilistener.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index cc9c3b5c8..201b22865 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -1554,7 +1554,7 @@ void ApiListener::ReplayLog(const JsonRpcConnection::Ptr& client) count++; } catch (const std::exception& ex) { Log(LogWarning, "ApiListener") - << "Error while replaying log for endpoint '" << endpoint->GetName() << "': " << DiagnosticInformation(ex, false); + << "Error while replaying log for endpoint '" << endpoint->GetName() << "': " << ex.what(); Log(LogDebug, "ApiListener") << "Error while replaying log for endpoint '" << endpoint->GetName() << "': " << DiagnosticInformation(ex); From 1c34610a782379ec2f5d006d145f2cb5754beee4 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 31 Oct 2024 14:19:05 +0100 Subject: [PATCH 091/415] JsonRpcConnection: Don't read any data on shutdown When the `Desconnect()` method is called, clients are not disconnected immediately. Instead, a new coroutine is spawned using the same strand as the other coroutines. This coroutine calls `async_shutdown` on the TCP socket, which might be blocking. However, in order not to block indefintely, the `Timeout` class cancels all operations on the socket after `10` seconds. Though, the timeout does not trigger the handler immediately; it creates spawns another coroutine using the same strand as in the `JsonRpcConnection` class. This can cause unexpected delays if e.g. `HandleIncomingMessages` gets resumed before the coroutine from the timeout class. Apart from that, the coroutine for writing messages uses the same condition, making the two symmetrical. --- lib/remote/jsonrpcconnection.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/remote/jsonrpcconnection.cpp b/lib/remote/jsonrpcconnection.cpp index 7f05f4498..cd684af6e 100644 --- a/lib/remote/jsonrpcconnection.cpp +++ b/lib/remote/jsonrpcconnection.cpp @@ -62,7 +62,7 @@ void JsonRpcConnection::HandleIncomingMessages(boost::asio::yield_context yc) { m_Stream->next_layer().SetSeen(&m_Seen); - for (;;) { + while (!m_ShuttingDown) { String message; try { From a77259adc1ad02ffd0bed28e50cc52f40cf37cf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Tue, 5 Nov 2024 13:15:22 +0100 Subject: [PATCH 092/415] Atomic#Atomic(T): fix C++ compliance by not calling `std::atomic::atomic(void)`. After the latter the instance "does not contain a T object, and its only valid uses are destruction and initialization by std::atomic_init" which we don't call. So the only safe option is `std::atomic::atomic(T)`. https://en.cppreference.com/w/cpp/atomic/atomic/atomic --- lib/base/atomic.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/base/atomic.hpp b/lib/base/atomic.hpp index c8f169c0b..bf0aecdc6 100644 --- a/lib/base/atomic.hpp +++ b/lib/base/atomic.hpp @@ -24,7 +24,7 @@ public: * * @param desired Initial value */ - inline Atomic(T desired) + inline Atomic(T desired) : std::atomic(desired) { this->store(desired); } @@ -35,7 +35,7 @@ public: * @param desired Initial value * @param order Initial store operation's memory order */ - inline Atomic(T desired, std::memory_order order) + inline Atomic(T desired, std::memory_order order) : std::atomic(desired) { this->store(desired, order); } From fb64c4f057eb73133cc05250761bcd465a84dba8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Wed, 6 Nov 2024 11:37:02 +0100 Subject: [PATCH 093/415] Atomic#Atomic(): remove superfluous atomic write --- lib/base/atomic.hpp | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/lib/base/atomic.hpp b/lib/base/atomic.hpp index bf0aecdc6..855850336 100644 --- a/lib/base/atomic.hpp +++ b/lib/base/atomic.hpp @@ -12,7 +12,12 @@ namespace icinga { /** - * Extends std::atomic with an atomic constructor. + * Like std::atomic, but enforces usage of its only safe constructor. + * + * "The default-initialized std::atomic does not contain a T object, + * and its only valid uses are destruction and + * initialization by std::atomic_init, see LWG issue 2334." + * -- https://en.cppreference.com/w/cpp/atomic/atomic/atomic * * @ingroup base */ @@ -20,24 +25,12 @@ template class Atomic : public std::atomic { public: /** - * Like std::atomic#atomic, but operates atomically + * The only safe constructor of std::atomic#atomic * * @param desired Initial value */ inline Atomic(T desired) : std::atomic(desired) { - this->store(desired); - } - - /** - * Like std::atomic#atomic, but operates atomically - * - * @param desired Initial value - * @param order Initial store operation's memory order - */ - inline Atomic(T desired, std::memory_order order) : std::atomic(desired) - { - this->store(desired, order); } }; From aa7f159a0fa5a0c5646165e91aaf5d0c1071c740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Thu, 7 Nov 2024 17:32:12 +0100 Subject: [PATCH 094/415] JsonRpcConnection: don't write new messages on shutdown In fact, this is already done for the outer loop (for each bulk), just not yet for the inner one (for each message of a bulk). So once the remote signals EOF, don't try to process the remaining queue until write error (which can't be associated with a particular message anyway, due to buffering), but just let the peer go. Flush already half-written messages, though, if possible. --- lib/remote/jsonrpcconnection.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/remote/jsonrpcconnection.cpp b/lib/remote/jsonrpcconnection.cpp index cd684af6e..92c74e301 100644 --- a/lib/remote/jsonrpcconnection.cpp +++ b/lib/remote/jsonrpcconnection.cpp @@ -110,6 +110,10 @@ void JsonRpcConnection::WriteOutgoingMessages(boost::asio::yield_context yc) if (!queue.empty()) { try { for (auto& message : queue) { + if (m_ShuttingDown) { + break; + } + size_t bytesSent = JsonRpc::SendRawMessage(m_Stream, message, yc); if (m_Endpoint) { From 09160ea9eba995ec748b56e08dc4b7632abc5c9d Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 11 Nov 2024 16:31:04 +0100 Subject: [PATCH 095/415] Make icinga::Empty constant to prevent accidental changes --- lib/base/object.hpp | 2 +- lib/base/value.cpp | 2 +- lib/base/value.hpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/base/object.hpp b/lib/base/object.hpp index 5a90cfa64..aae28ae88 100644 --- a/lib/base/object.hpp +++ b/lib/base/object.hpp @@ -27,7 +27,7 @@ class String; struct DebugInfo; class ValidationUtils; -extern Value Empty; +extern const Value Empty; #define DECLARE_PTR_TYPEDEFS(klass) \ typedef intrusive_ptr Ptr diff --git a/lib/base/value.cpp b/lib/base/value.cpp index 867c8217e..ebf3ba60b 100644 --- a/lib/base/value.cpp +++ b/lib/base/value.cpp @@ -13,7 +13,7 @@ template const bool& Value::Get() const; template const String& Value::Get() const; template const Object::Ptr& Value::Get() const; -Value icinga::Empty; +const Value icinga::Empty; Value::Value(std::nullptr_t) { } diff --git a/lib/base/value.hpp b/lib/base/value.hpp index 86a3b115d..6e64abb43 100644 --- a/lib/base/value.hpp +++ b/lib/base/value.hpp @@ -149,7 +149,7 @@ extern template const bool& Value::Get() const; extern template const String& Value::Get() const; extern template const Object::Ptr& Value::Get() const; -extern Value Empty; +extern const Value Empty; Value operator+(const Value& lhs, const char *rhs); Value operator+(const char *lhs, const Value& rhs); From d9b280be7b200ad5fad923e2cc05682c24545fdd Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 12 Nov 2024 16:04:47 +0000 Subject: [PATCH 096/415] CHANGELOG.md: add v2.11.12 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f808c2f5f..4a9509f28 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -967,6 +967,15 @@ Thanks to all contributors: * Code quality fixes * Small documentation fixes +## 2.11.12 (2024-11-12) + +This security release fixes a TLS certificate validation bypass. +Given the severity of that issue, users are advised to upgrade all nodes immediately. + +* Security: fix TLS certificate validation bypass. CVE-2024-49369 +* Security: update OpenSSL shipped on Windows to v3.0.15. +* Windows: sign MSI packages with a certificate the OS trusts by default. + ## 2.11.11 (2021-08-19) The main focus of these versions is a security vulnerability in the TLS certificate verification of our metrics writers ElasticsearchWriter, GelfWriter and InfluxdbWriter. From fa480f225a05a529f4ef99c1c3664383cf784bfa Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 12 Nov 2024 17:46:17 +0000 Subject: [PATCH 097/415] CHANGELOG.md: add v2.13.10 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a9509f28..fd231d320 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -234,6 +234,15 @@ Add `linux_netdev` check command. #9045 * Several code quality improvements. #8815 #9106 #9250 #9508 #9517 #9537 #9594 #9605 #9606 #9641 #9658 #9702 #9717 #9738 +## 2.13.10 (2024-11-12) + +This security release fixes a TLS certificate validation bypass. +Given the severity of that issue, users are advised to upgrade all nodes immediately. + +* Security: fix TLS certificate validation bypass. CVE-2024-49369 +* Security: update OpenSSL shipped on Windows to v3.0.15. +* Windows: sign MSI packages with a certificate the OS trusts by default. + ## 2.13.9 (2023-12-21) Version 2.13.9 is a hotfix release for masters and satellites that mainly From dfa29129836a5c87a7d295d84a676f4579aae1d2 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 12 Nov 2024 19:21:36 +0000 Subject: [PATCH 098/415] CHANGELOG.md: add v2.14.3 --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fd231d320..b9b9d08ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ documentation before upgrading to a new release. Released closed milestones can be found on [GitHub](https://github.com/Icinga/icinga2/milestones?state=closed). +## 2.14.3 (2024-11-12) + +This security release fixes a TLS certificate validation bypass. +Given the severity of that issue, users are advised to upgrade all nodes immediately. + +* Security: fix TLS certificate validation bypass. CVE-2024-49369 +* Security: update OpenSSL shipped on Windows to v3.0.15. +* Windows: sign MSI packages with a certificate the OS trusts by default. + ## 2.14.2 (2024-01-18) Version 2.14.2 is a hotfix release for master nodes that mainly From e620f9515b56a749038e8975c64125ed38d37ccd Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Wed, 13 Nov 2024 09:36:06 +0100 Subject: [PATCH 099/415] ITL: Add --exclude-process to check_procs For check_procs, both the Monitoring Plugins' implementation[0] and the Nagios Plugin[1] are supporting the "-X" or "--exclude-process" flag to exclude one or many processes by name. However, this flag is missing here in the Icinga Template Library. The Nagios Plugin implementation also comes with "-j" and "-g" for FreeBSD jails and Linux cgroups, respectively. But, to keep it compatible, I would ignore these for the moment. Closes #10226. [0]: https://www.monitoring-plugins.org/doc/man/check_procs.html [1]: https://nagios-plugins.org/doc/man/check_procs.html --- doc/10-icinga-template-library.md | 35 ++++++++++++++++--------------- itl/command-plugins.conf | 4 ++++ 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md index 249e43ff4..079612d42 100644 --- a/doc/10-icinga-template-library.md +++ b/doc/10-icinga-template-library.md @@ -1225,23 +1225,24 @@ of processes. Search filters can be applied to limit the processes to check. Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description ----------------------|-------------- -procs_warning | **Optional.** The process count warning threshold. Defaults to 250. -procs_critical | **Optional.** The process count critical threshold. Defaults to 400. -procs_metric | **Optional.** Check thresholds against metric. -procs_timeout | **Optional.** Seconds before plugin times out. -procs_traditional | **Optional.** Filter own process the traditional way by PID instead of /proc/pid/exe. Defaults to false. -procs_state | **Optional.** Only scan for processes that have one or more of the status flags you specify. -procs_ppid | **Optional.** Only scan for children of the parent process ID indicated. -procs_vsz | **Optional.** Only scan for processes with VSZ higher than indicated. -procs_rss | **Optional.** Only scan for processes with RSS higher than indicated. -procs_pcpu | **Optional.** Only scan for processes with PCPU higher than indicated. -procs_user | **Optional.** Only scan for processes with user name or ID indicated. -procs_argument | **Optional.** Only scan for processes with args that contain STRING. -procs_argument_regex | **Optional.** Only scan for processes with args that contain the regex STRING. -procs_command | **Optional.** Only scan for exact matches of COMMAND (without path). -procs_nokthreads | **Optional.** Only scan for non kernel threads. Defaults to false. +Name | Description +----------------------|-------------- +procs_warning | **Optional.** The process count warning threshold. Defaults to 250. +procs_critical | **Optional.** The process count critical threshold. Defaults to 400. +procs_metric | **Optional.** Check thresholds against metric. +procs_timeout | **Optional.** Seconds before plugin times out. +procs_traditional | **Optional.** Filter own process the traditional way by PID instead of /proc/pid/exe. Defaults to false. +procs_state | **Optional.** Only scan for processes that have one or more of the status flags you specify. +procs_ppid | **Optional.** Only scan for children of the parent process ID indicated. +procs_vsz | **Optional.** Only scan for processes with VSZ higher than indicated. +procs_rss | **Optional.** Only scan for processes with RSS higher than indicated. +procs_pcpu | **Optional.** Only scan for processes with PCPU higher than indicated. +procs_user | **Optional.** Only scan for processes with user name or ID indicated. +procs_argument | **Optional.** Only scan for processes with args that contain STRING. +procs_argument_regex | **Optional.** Only scan for processes with args that contain the regex STRING. +procs_command | **Optional.** Only scan for exact matches of COMMAND (without path). +procs_exclude_process | **Optional.** Exclude processes which match this comma separated list. +procs_nokthreads | **Optional.** Only scan for non kernel threads. Defaults to false. ### radius diff --git a/itl/command-plugins.conf b/itl/command-plugins.conf index 716dde757..5fc9ab435 100644 --- a/itl/command-plugins.conf +++ b/itl/command-plugins.conf @@ -1842,6 +1842,10 @@ object CheckCommand "procs" { value = "$procs_command$" description = "Only scan for exact matches of COMMAND (without path)" } + "-X" = { + value = "$procs_exclude_process$" + description = "Exclude processes which match this comma separated list" + } "-k" = { set_if = "$procs_nokthreads$" description = "Only scan for non kernel threads" From 5c0f9bfdaa649350b500b1837610c64cf24022f3 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 4 Nov 2024 11:26:10 +0100 Subject: [PATCH 100/415] HttpServerConnection: Don't spawn useless coroutines Currently, for each `Disconnect()` call, we spawn a coroutine, but every one of them is just usesless, except the first one. However, since all `Disconnect()` usages share the same asio strand and cannot interfere with each other, spawning another coroutine within `Disconnect()` isn't even necessary. When a coroutine calls `Disconnect()` now, it will immediately initiate an async shutdown of the socket, potentially causing the coroutine to yield and allowing the others to resume. Therefore, the `m_ShuttingDown` flag is still required by the coroutines to be checked regularly. --- lib/remote/httpserverconnection.cpp | 63 ++++++++++++++++------------- lib/remote/httpserverconnection.hpp | 3 +- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/lib/remote/httpserverconnection.cpp b/lib/remote/httpserverconnection.cpp index b9a8ab814..bb5831f4a 100644 --- a/lib/remote/httpserverconnection.cpp +++ b/lib/remote/httpserverconnection.cpp @@ -68,44 +68,49 @@ void HttpServerConnection::Start() IoEngine::SpawnCoroutine(m_IoStrand, [this, keepAlive](asio::yield_context yc) { CheckLiveness(yc); }); } -void HttpServerConnection::Disconnect() +/** + * Tries to asynchronously shut down the SSL stream and underlying socket. + * + * It is important to note that this method should only be called from within a coroutine that uses `m_IoStrand`. + * + * @param yc boost::asio::yield_context The coroutine yield context which you are calling this method from. + */ +void HttpServerConnection::Disconnect(boost::asio::yield_context yc) { namespace asio = boost::asio; - HttpServerConnection::Ptr keepAlive (this); + if (m_ShuttingDown) { + return; + } - IoEngine::SpawnCoroutine(m_IoStrand, [this, keepAlive](asio::yield_context yc) { - if (!m_ShuttingDown) { - m_ShuttingDown = true; + m_ShuttingDown = true; - Log(LogInformation, "HttpServerConnection") - << "HTTP client disconnected (from " << m_PeerAddress << ")"; + Log(LogInformation, "HttpServerConnection") + << "HTTP client disconnected (from " << m_PeerAddress << ")"; - /* - * Do not swallow exceptions in a coroutine. - * https://github.com/Icinga/icinga2/issues/7351 - * We must not catch `detail::forced_unwind exception` as - * this is used for unwinding the stack. - * - * Just use the error_code dummy here. - */ - boost::system::error_code ec; + /* + * Do not swallow exceptions in a coroutine. + * https://github.com/Icinga/icinga2/issues/7351 + * We must not catch `detail::forced_unwind exception` as + * this is used for unwinding the stack. + * + * Just use the error_code dummy here. + */ + boost::system::error_code ec; - m_CheckLivenessTimer.cancel(); + m_CheckLivenessTimer.cancel(); - m_Stream->lowest_layer().cancel(ec); + m_Stream->lowest_layer().cancel(ec); - m_Stream->next_layer().async_shutdown(yc[ec]); + m_Stream->next_layer().async_shutdown(yc[ec]); - m_Stream->lowest_layer().shutdown(m_Stream->lowest_layer().shutdown_both, ec); + m_Stream->lowest_layer().shutdown(m_Stream->lowest_layer().shutdown_both, ec); - auto listener (ApiListener::GetInstance()); + auto listener (ApiListener::GetInstance()); - if (listener) { - listener->RemoveHttpClient(this); - } - } - }); + if (listener) { + listener->RemoveHttpClient(this); + } } void HttpServerConnection::StartStreaming() @@ -126,7 +131,7 @@ void HttpServerConnection::StartStreaming() m_Stream->async_read_some(readBuf, yc[ec]); } while (!ec); - Disconnect(); + Disconnect(yc); } }); } @@ -563,7 +568,7 @@ void HttpServerConnection::ProcessMessages(boost::asio::yield_context yc) } } - Disconnect(); + Disconnect(yc); } void HttpServerConnection::CheckLiveness(boost::asio::yield_context yc) @@ -582,7 +587,7 @@ void HttpServerConnection::CheckLiveness(boost::asio::yield_context yc) Log(LogInformation, "HttpServerConnection") << "No messages for HTTP connection have been received in the last 10 seconds."; - Disconnect(); + Disconnect(yc); break; } } diff --git a/lib/remote/httpserverconnection.hpp b/lib/remote/httpserverconnection.hpp index 9c812e526..63f99e19c 100644 --- a/lib/remote/httpserverconnection.hpp +++ b/lib/remote/httpserverconnection.hpp @@ -28,7 +28,6 @@ public: HttpServerConnection(const String& identity, bool authenticated, const Shared::Ptr& stream); void Start(); - void Disconnect(); void StartStreaming(); bool Disconnected(); @@ -45,6 +44,8 @@ private: HttpServerConnection(const String& identity, bool authenticated, const Shared::Ptr& stream, boost::asio::io_context& io); + void Disconnect(boost::asio::yield_context yc); + void ProcessMessages(boost::asio::yield_context yc); void CheckLiveness(boost::asio::yield_context yc); }; From 0bbe7a9b2f438035b836dc97f4a330082a71c659 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Fri, 15 Nov 2024 12:56:45 +0100 Subject: [PATCH 101/415] IcingaDB Check: Multiple Responsible Instances By design, only one Icinga 2 instance should be responsible in the HA context. If this promise is broken, the Icinga 2 IcingaDB check should report it. The code did not check for invalid data in icingadb:telemetry:heartbeat. With this change, it will go CRITICAL with a descriptive message and report the actual number of icingadb_responsible_instances in the performance data. --- lib/icingadb/icingadbchecktask.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/icingadb/icingadbchecktask.cpp b/lib/icingadb/icingadbchecktask.cpp index f7c596457..cee93cd33 100644 --- a/lib/icingadb/icingadbchecktask.cpp +++ b/lib/icingadb/icingadbchecktask.cpp @@ -227,7 +227,9 @@ void IcingadbCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckR perfdata->Add(new PerfdataValue("icinga2_heartbeat_age", heartbeatLag, false, "seconds", heartbeatLagWarning, Empty, 0)); } - if (weResponsible) { + if (weResponsible && otherResponsible) { + critmsgs << " Both this instance and another instance are responsible!"; + } else if (weResponsible) { idbokmsgs << "\n* Responsible"; } else if (otherResponsible) { idbokmsgs << "\n* Not responsible, but another instance is"; @@ -235,7 +237,7 @@ void IcingadbCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckR critmsgs << " No instance is responsible!"; } - perfdata->Add(new PerfdataValue("icingadb_responsible_instances", int(weResponsible || otherResponsible), false, "", Empty, Empty, 0, 1)); + perfdata->Add(new PerfdataValue("icingadb_responsible_instances", int(weResponsible) + int(otherResponsible), false, "", Empty, Empty, 0, 1)); const auto clockDriftWarning (5); const auto clockDriftCritical (30); From 35a705752ff765b0005146d7966e2ddb3896cf0c Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 14 Mar 2024 16:29:49 +0100 Subject: [PATCH 102/415] Don't set Notification#no_more_notifications on custom notifications --- lib/icinga/notification.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/icinga/notification.cpp b/lib/icinga/notification.cpp index 81a48bada..bd3158ced 100644 --- a/lib/icinga/notification.cpp +++ b/lib/icinga/notification.cpp @@ -389,7 +389,7 @@ void Notification::BeginExecuteNotification(NotificationType type, const CheckRe if (type == NotificationProblem && GetInterval() <= 0) SetNoMoreNotifications(true); - else + else if (type != NotificationCustom) SetNoMoreNotifications(false); if (type == NotificationProblem && GetInterval() > 0) From a19246aca75a4e89eb874b4c50a73f124cd487af Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 25 Nov 2024 13:10:26 +0100 Subject: [PATCH 103/415] GHA: Drop ubuntu 23.{04,10} (EOL) --- .github/workflows/linux.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 68878fdfe..bb80ebbe9 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -37,8 +37,6 @@ jobs: - rockylinux:9 # RHEL 9 - ubuntu:20.04 - ubuntu:22.04 - - ubuntu:23.04 - - ubuntu:23.10 - ubuntu:24.04 - ubuntu:24.10 From b7335841a36043ee6c4867d9c8394fca983a85f6 Mon Sep 17 00:00:00 2001 From: Christian Lauf Date: Tue, 26 Nov 2024 10:52:05 +0100 Subject: [PATCH 104/415] Enhance documentation regarding internal icinga config sync check (#10101) * Update 10-icinga-template-library.md Explicitly name the config-sync check feature of the icinga check, as before this was a little bit too undocumented making it unknown to me. Also mention where the check has to executed in order to bring the desired results. * Update 15-troubleshooting.md Add 4h typical error point for configuration stored outside of /etc/icinga2/zones.d. For when a non-distributed setup was migrated to a distributed setup. Also link to the internal icinga CheckCommand to promote its existance. * Update 15-troubleshooting.md Remove "-" from link * Revert "Update 15-troubleshooting.md" This reverts commit bb25ba3ff5d2797b95cc6c6d5d4fc64e342164f1. * Update AUTHORS Add myself to AUTHORS * Update doc/15-troubleshooting.md Co-authored-by: alvar <8402811+oxzi@users.noreply.github.com> * Update doc/10-icinga-template-library.md Co-authored-by: alvar <8402811+oxzi@users.noreply.github.com> * Update doc/15-troubleshooting.md Co-authored-by: alvar <8402811+oxzi@users.noreply.github.com> --------- Co-authored-by: alvar <8402811+oxzi@users.noreply.github.com> --- AUTHORS | 1 + doc/10-icinga-template-library.md | 6 ++++-- doc/15-troubleshooting.md | 3 +++ 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/AUTHORS b/AUTHORS index 91aa37382..b73f96492 100644 --- a/AUTHORS +++ b/AUTHORS @@ -52,6 +52,7 @@ Christian Gut Christian Harke Christian Jonak Christian Lehmann +Christian Lauf Christian Loos Christian Schmidt Christopher Peterson <3893680+cspeterson@users.noreply.github.com> diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md index 079612d42..599bfe20b 100644 --- a/doc/10-icinga-template-library.md +++ b/doc/10-icinga-template-library.md @@ -75,8 +75,10 @@ plugin scripts. ### icinga -Check command for the built-in `icinga` check. This check returns performance -data for the current Icinga instance, reports as warning if the last reload failed and optionally allows for minimum version checks. +Check command for the built-in `icinga` check. This check returns performance data for the current Icinga instance, +reports as warning if the last reload or config sync failed and optionally allows for minimum version checks. + +For the config sync check to work, it must be run on the satellite or agent. Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): diff --git a/doc/15-troubleshooting.md b/doc/15-troubleshooting.md index 945ceed1e..5afbebea9 100644 --- a/doc/15-troubleshooting.md +++ b/doc/15-troubleshooting.md @@ -1698,6 +1698,9 @@ Typical errors are: * The api feature doesn't [accept config](06-distributed-monitoring.md#distributed-monitoring-top-down-config-sync). This is logged into `/var/lib/icinga2/icinga2.log`. * The received configuration zone is not configured in [zones.conf](04-configuration.md#zones-conf) and Icinga denies it. This is logged into `/var/lib/icinga2/icinga2.log`. * The satellite/agent has local configuration in `/etc/icinga2/zones.d` and thinks it is authoritive for this zone. It then denies the received update. Purge the content from `/etc/icinga2/zones.d`, `/var/lib/icinga2/api/zones/*` and restart Icinga to fix this. +* Configuration parts stored outside of `/etc/icinga2/zones.d` on the master, for example a constant in `/etc/icinga2/constants.conf`, are then missing on the satellite/agent. + +Note that if set up, the [built-in icinga CheckCommand](10-icinga-template-library.md#icinga) will notify you in case the config sync wasn't successful. #### New configuration does not trigger a reload From 22b36b7cfb3bb9453fa28b7f17f87a8ac2a2d19d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Tue, 26 Nov 2024 11:15:53 +0100 Subject: [PATCH 105/415] GHA: update supported Fedora versions Add v41, drop EOL v37, v38. --- .github/workflows/linux.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 68878fdfe..39c1789d0 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -25,10 +25,9 @@ jobs: - amazonlinux:2023 - debian:11 # and Raspberry Pi OS 11 - debian:12 # and Raspberry Pi OS 12 - - fedora:37 - - fedora:38 - fedora:39 - fedora:40 + - fedora:41 - opensuse/leap:15.3 # SLES 15.3 - opensuse/leap:15.4 # and SLES 15.4 - opensuse/leap:15.5 # and SLES 15.5 From e0b053cbe11e78b54eb519ea08d904d3c31e5e76 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 28 Aug 2024 18:08:35 +0200 Subject: [PATCH 106/415] HttpServerConnection: Log noticable CPU semaphore wait time --- lib/remote/httpserverconnection.cpp | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/remote/httpserverconnection.cpp b/lib/remote/httpserverconnection.cpp index bb5831f4a..3f2573f42 100644 --- a/lib/remote/httpserverconnection.cpp +++ b/lib/remote/httpserverconnection.cpp @@ -433,13 +433,17 @@ bool ProcessRequest( boost::beast::http::response& response, HttpServerConnection& server, bool& hasStartedStreaming, + std::chrono::steady_clock::duration& cpuBoundWorkTime, boost::asio::yield_context& yc ) { namespace http = boost::beast::http; try { + // Cache the elapsed time to acquire a CPU semaphore used to detect extremely heavy workloads. + auto start (std::chrono::steady_clock::now()); CpuBoundWork handlingRequest (yc); + cpuBoundWorkTime = std::chrono::steady_clock::now() - start; HttpHandler::ProcessRequest(stream, authenticatedUser, request, response, yc, server); } catch (const std::exception& ex) { @@ -530,9 +534,14 @@ void HttpServerConnection::ProcessMessages(boost::asio::yield_context yc) << ", user: " << (authenticatedUser ? authenticatedUser->GetName() : "") << ", agent: " << request[http::field::user_agent]; //operator[] - Returns the value for a field, or "" if it does not exist. - Defer addRespCode ([&response, start, &logMsg]() { - logMsg << ", status: " << response.result() << ") took " - << ch::duration_cast(ch::steady_clock::now() - start).count() << "ms."; + ch::steady_clock::duration cpuBoundWorkTime(0); + Defer addRespCode ([&response, start, &logMsg, &cpuBoundWorkTime]() { + logMsg << ", status: " << response.result() << ")"; + if (cpuBoundWorkTime >= ch::seconds(1)) { + logMsg << " waited " << ch::duration_cast(cpuBoundWorkTime).count() << "ms on semaphore and"; + } + + logMsg << " took total " << ch::duration_cast(ch::steady_clock::now() - start).count() << "ms."; }); if (!HandleAccessControl(*m_Stream, request, response, yc)) { @@ -553,7 +562,7 @@ void HttpServerConnection::ProcessMessages(boost::asio::yield_context yc) m_Seen = std::numeric_limits::max(); - if (!ProcessRequest(*m_Stream, request, authenticatedUser, response, *this, m_HasStartedStreaming, yc)) { + if (!ProcessRequest(*m_Stream, request, authenticatedUser, response, *this, m_HasStartedStreaming, cpuBoundWorkTime, yc)) { break; } From 4564c068fe452a30bd8476705949e0c49eb1bb66 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 29 Aug 2024 14:14:20 +0200 Subject: [PATCH 107/415] JsonRpcConnection: Log message processing time stats Co-Authored-By: Julian Brost --- lib/remote/jsonrpcconnection.cpp | 66 +++++++++++++++++++++++++++----- lib/remote/jsonrpcconnection.hpp | 3 +- 2 files changed, 58 insertions(+), 11 deletions(-) diff --git a/lib/remote/jsonrpcconnection.cpp b/lib/remote/jsonrpcconnection.cpp index cd684af6e..f298bb075 100644 --- a/lib/remote/jsonrpcconnection.cpp +++ b/lib/remote/jsonrpcconnection.cpp @@ -60,13 +60,19 @@ void JsonRpcConnection::Start() void JsonRpcConnection::HandleIncomingMessages(boost::asio::yield_context yc) { + namespace ch = std::chrono; + + auto toMilliseconds ([](ch::steady_clock::duration d) { + return ch::duration_cast(d).count(); + }); + m_Stream->next_layer().SetSeen(&m_Seen); while (!m_ShuttingDown) { - String message; + String jsonString; try { - message = JsonRpc::ReadMessage(m_Stream, yc, m_Endpoint ? -1 : 1024 * 1024); + jsonString = JsonRpc::ReadMessage(m_Stream, yc, m_Endpoint ? -1 : 1024 * 1024); } catch (const std::exception& ex) { Log(m_ShuttingDown ? LogDebug : LogNotice, "JsonRpcConnection") << "Error while reading JSON-RPC message for identity '" << m_Identity @@ -76,17 +82,50 @@ void JsonRpcConnection::HandleIncomingMessages(boost::asio::yield_context yc) } m_Seen = Utility::GetTime(); + if (m_Endpoint) { + m_Endpoint->AddMessageReceived(jsonString.GetLength()); + } + + String rpcMethod("UNKNOWN"); + ch::steady_clock::duration cpuBoundDuration(0); + auto start (ch::steady_clock::now()); try { CpuBoundWork handleMessage (yc); + // Cache the elapsed time to acquire a CPU semaphore used to detect extremely heavy workloads. + cpuBoundDuration = ch::steady_clock::now() - start; + + Dictionary::Ptr message = JsonRpc::DecodeMessage(jsonString); + if (String method = message->Get("method"); !method.IsEmpty()) { + rpcMethod = std::move(method); + } + MessageHandler(message); l_TaskStats.InsertValue(Utility::GetTime(), 1); + + auto total = ch::steady_clock::now() - start; + + Log msg(total >= ch::seconds(5) ? LogWarning : LogDebug, "JsonRpcConnection"); + msg << "Processed JSON-RPC '" << rpcMethod << "' message for identity '" << m_Identity + << "' (took total " << toMilliseconds(total) << "ms"; + + if (cpuBoundDuration >= ch::seconds(1)) { + msg << ", waited " << toMilliseconds(cpuBoundDuration) << "ms on semaphore"; + } + msg << ")."; } catch (const std::exception& ex) { - Log(m_ShuttingDown ? LogDebug : LogWarning, "JsonRpcConnection") - << "Error while processing JSON-RPC message for identity '" << m_Identity - << "': " << DiagnosticInformation(ex); + auto total = ch::steady_clock::now() - start; + + Log msg(m_ShuttingDown ? LogDebug : LogWarning, "JsonRpcConnection"); + msg << "Error while processing JSON-RPC '" << rpcMethod << "' message for identity '" + << m_Identity << "' (took total " << toMilliseconds(total) << "ms"; + + if (cpuBoundDuration >= ch::seconds(1)) { + msg << ", waited " << toMilliseconds(cpuBoundDuration) << "ms on semaphore"; + } + msg << "): " << DiagnosticInformation(ex); break; } @@ -259,10 +298,19 @@ void JsonRpcConnection::Disconnect() } } -void JsonRpcConnection::MessageHandler(const String& jsonString) +/** + * Route the provided message to its corresponding handler (if any). + * + * This will first verify the timestamp of that RPC message (if any) and subsequently, rejects any message whose + * timestamp is less than the remote log position of the client Endpoint; otherwise, the endpoint's remote log + * position is updated to that timestamp. It is not expected to happen, but any message lacking an RPC method or + * referring to a non-existent one is also discarded. Afterward, the RPC handler is then called for that message + * and sends it's result back to the sender if the message contains an ID. + * + * @param message The RPC message you want to process. +*/ +void JsonRpcConnection::MessageHandler(const Dictionary::Ptr& message) { - Dictionary::Ptr message = JsonRpc::DecodeMessage(jsonString); - if (m_Endpoint && message->Contains("ts")) { double ts = message->Get("ts"); @@ -281,8 +329,6 @@ void JsonRpcConnection::MessageHandler(const String& jsonString) origin->FromZone = m_Endpoint->GetZone(); else origin->FromZone = Zone::GetByName(message->Get("originZone")); - - m_Endpoint->AddMessageReceived(jsonString.GetLength()); } Value vmethod; diff --git a/lib/remote/jsonrpcconnection.hpp b/lib/remote/jsonrpcconnection.hpp index bbe8588a1..ef83dce1b 100644 --- a/lib/remote/jsonrpcconnection.hpp +++ b/lib/remote/jsonrpcconnection.hpp @@ -88,7 +88,8 @@ private: void CheckLiveness(boost::asio::yield_context yc); bool ProcessMessage(); - void MessageHandler(const String& jsonString); + + void MessageHandler(const Dictionary::Ptr& message); void CertificateRequestResponseHandler(const Dictionary::Ptr& message); From 501175229c54286c22971f31d243f03b49a8a5f2 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 18 Jun 2024 15:06:34 +0200 Subject: [PATCH 108/415] Doc: Distributed Monitoring: add section "External CA/PKI" The following already works: * Custom key sizes, e.g. 2048 bits * Custom key types, e.g. ECC * Multiple trusted root CAs in `/var/lib/icinga2/certs/ca.crt` * Different root CAs per cluster subtree, as long as each node trusts the issuers of the certificates of all nodes it's directly connected to * Any number of intermediate CAs --- doc/06-distributed-monitoring.md | 47 ++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/doc/06-distributed-monitoring.md b/doc/06-distributed-monitoring.md index b81f22b4b..b05f4a8c8 100644 --- a/doc/06-distributed-monitoring.md +++ b/doc/06-distributed-monitoring.md @@ -3227,6 +3227,53 @@ information/pki: Writing certificate to file 'icinga2-satellite1.localdomain.crt Copy and move these certificates to the respective instances e.g. with SSH/SCP. +#### External CA/PKI + +Icinga works best with its own certificates. +The commands described above take care of the optimal certificate properties. +Also, Icinga renews them periodically at runtime to avoid expiry. +But you can also provide your own certificates, +just like to any other application which uses TLS. + +!!! warning + + The only serious reasons to generate own certificates are company policies. + You are responsible for making Icinga working with your certificates, + as well as for [expiry monitoring](10-icinga-template-library.md#plugin-check-command-ssl_cert) + and renewal. + + Especially `icinga2 pki` CLI commands do not expect such certificates. + + Also, do not provide your custom CA private key to Icinga 2! + Otherwise, it will automatically renew leaf certificates + with our hardcoded properties, not your custom ones. + +The CA certificate must be located in `/var/lib/icinga2/certs/ca.crt`. +The basic requirements for all leaf certificates are: + +* Located in `/var/lib/icinga2/certs/NODENAME.crt` + and `/var/lib/icinga2/certs/NODENAME.key` +* Subject with CN matching the endpoint name +* A DNS SAN matching the endpoint name + +Pretty much everything else is limited only by your company policy +and the OpenSSL versions your Icinga nodes use. E.g. the following works: + +* Custom key sizes, e.g. 2048 bits +* Custom key types, e.g. ECC +* Any number of intermediate CAs (but see limitations below) +* Multiple trusted root CAs in `/var/lib/icinga2/certs/ca.crt` +* Different root CAs per cluster subtree, as long as each node trusts the + certificate issuers of all nodes it's directly connected to + +Intermediate CA restrictions: + +* Each side has to provide its intermediate CAs along with the leaf certificate + in `/var/lib/icinga2/certs/NODENAME.crt`, ordered from leaf to root. +* Intermediate CAs may not be used directly as root CAs. To trust only specific + intermediate CAs, cross-sign them with themselves, so that you get equal + certificates except that they're self-signed. Use them as root CAs in Icinga. + ## Automation These hints should get you started with your own automation tools (Puppet, Ansible, Chef, Salt, etc.) From cca5f6603b325a1084746186b369186f3c4895c7 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 27 Nov 2024 15:20:28 +0100 Subject: [PATCH 109/415] GHA: Linux: don't track all supported distro versions Instead just give a generic explanation per distro. --- .github/workflows/linux.yml | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index f3fb5d8dd..039c460d3 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -23,17 +23,30 @@ jobs: distro: - amazonlinux:2 - amazonlinux:2023 - - debian:11 # and Raspberry Pi OS 11 - - debian:12 # and Raspberry Pi OS 12 + + # Raspberry Pi OS is close enough to Debian to test just one of them. + # Its architecture is different, though, and covered by the Docker job. + - debian:11 + - debian:12 + - fedora:39 - fedora:40 - fedora:41 - - opensuse/leap:15.3 # SLES 15.3 - - opensuse/leap:15.4 # and SLES 15.4 - - opensuse/leap:15.5 # and SLES 15.5 - - opensuse/leap:15.6 # and SLES 15.6 - - rockylinux:8 # RHEL 8 - - rockylinux:9 # RHEL 9 + + # openSUSE Leap is close enough to SLES to test just one of them. + # As openSUSE is much easier to deploy, we test it despite the fact that we don't necessarily + # support individual versions of openSUSE as long as their SLES counterparts. + # I.e., remove any opensuse/leap:* line below only if we stopped packaging both openSUSE and SLES! + - opensuse/leap:15.3 + - opensuse/leap:15.4 + - opensuse/leap:15.5 + - opensuse/leap:15.6 + + # We don't actually support Rocky Linux as such! + # We just use that RHEL clone to test the original. + - rockylinux:8 + - rockylinux:9 + - ubuntu:20.04 - ubuntu:22.04 - ubuntu:24.04 From e881898ce04e820838f959b09f1253fcc46c1e75 Mon Sep 17 00:00:00 2001 From: Nicolas Rodriguez Date: Sat, 30 Nov 2024 02:56:18 +0100 Subject: [PATCH 110/415] Add missing option "--unplugged_nics_state" to vmware-esx-soap-host-net and vmware-esx-soap-host-net-nic --- AUTHORS | 1 + doc/10-icinga-template-library.md | 70 ++++++++++++++++--------------- itl/plugins-contrib.d/vmware.conf | 8 ++++ 3 files changed, 45 insertions(+), 34 deletions(-) diff --git a/AUTHORS b/AUTHORS index b73f96492..483c1aff6 100644 --- a/AUTHORS +++ b/AUTHORS @@ -213,6 +213,7 @@ nemtrif Nicolai Nicolas Berens Nicolas Limage +Nicolas Rodriguez Nicole Lang Niflou Noah Hilverling diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md index 599bfe20b..f20832437 100644 --- a/doc/10-icinga-template-library.md +++ b/doc/10-icinga-template-library.md @@ -4393,23 +4393,24 @@ Check command object for the `check_vmware_esx` plugin. Shows net info. Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_exclude | **Optional.** Blacklist NICs. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist expression as regexp. +Name | Description +----------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_exclude | **Optional.** Blacklist NICs. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist expression as regexp. +vmware_unplugged_nics_state | **Optional.** Sets status for unplugged nics (Possible values are: [OK | ok] or [CRITICAL | critical | CRIT | crit] or [WARNING | warning | WARN | warn]. Default is WARNING. Values are case insensitive.) **vmware-esx-soap-host-net-usage** @@ -4493,23 +4494,24 @@ Check command object for the `check_vmware_esx` plugin. Check all active NICs. Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_exclude | **Optional.** Blacklist NICs. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist expression as regexp. +Name | Description +----------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_exclude | **Optional.** Blacklist NICs. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist expression as regexp. +vmware_unplugged_nics_state | **Optional.** Sets status for unplugged nics (Possible values are: [OK | ok] or [CRITICAL | critical | CRIT | crit] or [WARNING | warning | WARN | warn]. Default is WARNING. Values are case insensitive.) **vmware-esx-soap-host-volumes** diff --git a/itl/plugins-contrib.d/vmware.conf b/itl/plugins-contrib.d/vmware.conf index 7017c8386..025cedb11 100644 --- a/itl/plugins-contrib.d/vmware.conf +++ b/itl/plugins-contrib.d/vmware.conf @@ -421,6 +421,10 @@ object CheckCommand "vmware-esx-soap-host-net" { "--isregexp" = { set_if = "$vmware_isregexp$" } + "--unplugged_nics_state" = { + value = "$vmware_unplugged_nics_state$" + description = "Sets status for unplugged nics (Possible values are: [OK | ok] or [CRITICAL | critical | CRIT | crit] or [WARNING | warning | WARN | warn]. Default is WARNING. Values are case insensitive.)" + } } } @@ -467,6 +471,10 @@ object CheckCommand "vmware-esx-soap-host-net-nic" { "--isregexp" = { set_if = "$vmware_isregexp$" } + "--unplugged_nics_state" = { + value = "$vmware_unplugged_nics_state$" + description = "Sets status for unplugged nics (Possible values are: [OK | ok] or [CRITICAL | critical | CRIT | crit] or [WARNING | warning | WARN | warn]. Default is WARNING. Values are case insensitive.)" + } } } From 188ba53b74c530b323b9b45de34882d9444175af Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 4 Dec 2024 10:57:30 +0100 Subject: [PATCH 111/415] DependencyGraph: switch "parent" and "child" terminology The .ti files call `DependencyGraph::AddDependency(this, service.get())`. Obviously, `service.get()` is the parent and `this` (Downtime, Notification, ...) is the child. The DependencyGraph terminology should reflect this not to confuse its future users. --- lib/base/dependencygraph.cpp | 16 ++++++++-------- lib/base/dependencygraph.hpp | 6 +++--- lib/base/scriptutils.cpp | 2 +- lib/remote/configobjectutility.cpp | 2 +- lib/remote/objectqueryhandler.cpp | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/lib/base/dependencygraph.cpp b/lib/base/dependencygraph.cpp index 025eb3ea3..88cb18194 100644 --- a/lib/base/dependencygraph.cpp +++ b/lib/base/dependencygraph.cpp @@ -7,18 +7,18 @@ using namespace icinga; std::mutex DependencyGraph::m_Mutex; std::map > DependencyGraph::m_Dependencies; -void DependencyGraph::AddDependency(Object *parent, Object *child) +void DependencyGraph::AddDependency(Object* child, Object* parent) { std::unique_lock lock(m_Mutex); - m_Dependencies[child][parent]++; + m_Dependencies[parent][child]++; } -void DependencyGraph::RemoveDependency(Object *parent, Object *child) +void DependencyGraph::RemoveDependency(Object* child, Object* parent) { std::unique_lock lock(m_Mutex); - auto& refs = m_Dependencies[child]; - auto it = refs.find(parent); + auto& refs = m_Dependencies[parent]; + auto it = refs.find(child); if (it == refs.end()) return; @@ -29,15 +29,15 @@ void DependencyGraph::RemoveDependency(Object *parent, Object *child) refs.erase(it); if (refs.empty()) - m_Dependencies.erase(child); + m_Dependencies.erase(parent); } -std::vector DependencyGraph::GetParents(const Object::Ptr& child) +std::vector DependencyGraph::GetChildren(const Object::Ptr& parent) { std::vector objects; std::unique_lock lock(m_Mutex); - auto it = m_Dependencies.find(child.get()); + auto it = m_Dependencies.find(parent.get()); if (it != m_Dependencies.end()) { typedef std::pair kv_pair; diff --git a/lib/base/dependencygraph.hpp b/lib/base/dependencygraph.hpp index 51aa90eca..43dd97c08 100644 --- a/lib/base/dependencygraph.hpp +++ b/lib/base/dependencygraph.hpp @@ -18,9 +18,9 @@ namespace icinga { class DependencyGraph { public: - static void AddDependency(Object *parent, Object *child); - static void RemoveDependency(Object *parent, Object *child); - static std::vector GetParents(const Object::Ptr& child); + static void AddDependency(Object* child, Object* parent); + static void RemoveDependency(Object* child, Object* parent); + static std::vector GetChildren(const Object::Ptr& parent); private: DependencyGraph(); diff --git a/lib/base/scriptutils.cpp b/lib/base/scriptutils.cpp index 7fe856de3..5bf2164a7 100644 --- a/lib/base/scriptutils.cpp +++ b/lib/base/scriptutils.cpp @@ -520,7 +520,7 @@ String ScriptUtils::MsiGetComponentPathShim(const String& component) Array::Ptr ScriptUtils::TrackParents(const Object::Ptr& child) { - return Array::FromVector(DependencyGraph::GetParents(child)); + return Array::FromVector(DependencyGraph::GetChildren(child)); } double ScriptUtils::Ptr(const Object::Ptr& object) diff --git a/lib/remote/configobjectutility.cpp b/lib/remote/configobjectutility.cpp index 60268f6e1..75dcfab93 100644 --- a/lib/remote/configobjectutility.cpp +++ b/lib/remote/configobjectutility.cpp @@ -303,7 +303,7 @@ bool ConfigObjectUtility::CreateObject(const Type::Ptr& type, const String& full bool ConfigObjectUtility::DeleteObjectHelper(const ConfigObject::Ptr& object, bool cascade, const Array::Ptr& errors, const Array::Ptr& diagnosticInformation, const Value& cookie) { - std::vector parents = DependencyGraph::GetParents(object); + auto parents (DependencyGraph::GetChildren(object)); Type::Ptr type = object->GetReflectionType(); diff --git a/lib/remote/objectqueryhandler.cpp b/lib/remote/objectqueryhandler.cpp index ad73030da..4c40ef3ed 100644 --- a/lib/remote/objectqueryhandler.cpp +++ b/lib/remote/objectqueryhandler.cpp @@ -208,7 +208,7 @@ bool ObjectQueryHandler::HandleRequest( Array::Ptr used_by = new Array(); metaAttrs.emplace_back("used_by", used_by); - for (const Object::Ptr& pobj : DependencyGraph::GetParents((obj))) + for (auto& pobj : DependencyGraph::GetChildren(obj)) { ConfigObject::Ptr configObj = dynamic_pointer_cast(pobj); From 56d58112830d69a259464973f641b8403de552b0 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Mon, 12 Feb 2024 16:28:37 +0100 Subject: [PATCH 112/415] AsioTlsStream: add GracefulDisconnect() and ForceDisconnect() Calling `AsioTlsStream::async_shutdown()` performs a TLS shutdown which exchanges messages (that's why it takes a `yield_context`) and thus has the potential to block the coroutine. Therefore, it should be protected with a timeout. As `async_shutdown()` doesn't simply take a timeout, this has to be implemented using a timer. So far, these timers are scattered throughout the codebase with some places missing them entirely. This commit adds helper functions to properly shutdown a TLS connection with a single function call. --- lib/base/tlsstream.cpp | 64 ++++++++++++++++++++++++++++++++++++++++++ lib/base/tlsstream.hpp | 3 ++ 2 files changed, 67 insertions(+) diff --git a/lib/base/tlsstream.cpp b/lib/base/tlsstream.cpp index a71451a53..ed8005837 100644 --- a/lib/base/tlsstream.cpp +++ b/lib/base/tlsstream.cpp @@ -7,6 +7,8 @@ #include "base/logger.hpp" #include "base/configuration.hpp" #include "base/convert.hpp" +#include "base/defer.hpp" +#include "base/io-engine.hpp" #include #include #include @@ -103,3 +105,65 @@ void UnbufferedAsioTlsStream::BeforeHandshake(handshake_type type) } #endif /* SSL_CTRL_SET_TLSEXT_HOSTNAME */ } + +/** + * Forcefully close the connection, typically (details are up to the operating system) using a TCP RST. + */ +void AsioTlsStream::ForceDisconnect() +{ + if (!lowest_layer().is_open()) { + // Already disconnected, nothing to do. + return; + } + + boost::system::error_code ec; + + // Close the socket. In case the connection wasn't shut down cleanly by GracefulDisconnect(), the operating system + // will typically terminate the connection with a TCP RST. Otherwise, this just releases the file descriptor. + lowest_layer().close(ec); +} + +/** + * Try to cleanly shut down the connection. This involves sending a TLS close_notify shutdown alert and terminating the + * underlying TCP connection. Sending these additional messages can block, hence the method takes a yield context and + * internally implements a timeout of 10 seconds for the operation after which the connection is forcefully terminated + * using ForceDisconnect(). + * + * @param strand Asio strand used for other operations on this connection. + * @param yc Yield context for Asio coroutines + */ +void AsioTlsStream::GracefulDisconnect(boost::asio::io_context::strand& strand, boost::asio::yield_context& yc) +{ + if (!lowest_layer().is_open()) { + // Already disconnected, nothing to do. + return; + } + + { + Timeout::Ptr shutdownTimeout(new Timeout(strand.context(), strand, boost::posix_time::seconds(10), + [this](boost::asio::yield_context yc) { + // Forcefully terminate the connection if async_shutdown() blocked more than 10 seconds. + ForceDisconnect(); + } + )); + Defer cancelTimeout ([&shutdownTimeout]() { + shutdownTimeout->Cancel(); + }); + + // Close the TLS connection, effectively uses SSL_shutdown() to send a close_notify shutdown alert to the peer. + boost::system::error_code ec; + next_layer().async_shutdown(yc[ec]); + } + + if (!lowest_layer().is_open()) { + // Connection got closed in the meantime, most likely by the timeout, so nothing more to do. + return; + } + + // Shut down the TCP connection. + boost::system::error_code ec; + lowest_layer().shutdown(lowest_layer_type::shutdown_both, ec); + + // Clean up the connection (closes the file descriptor). + ForceDisconnect(); +} diff --git a/lib/base/tlsstream.hpp b/lib/base/tlsstream.hpp index 9a6340baf..9eed8d3b1 100644 --- a/lib/base/tlsstream.hpp +++ b/lib/base/tlsstream.hpp @@ -111,6 +111,9 @@ public: { } + void ForceDisconnect(); + void GracefulDisconnect(boost::asio::io_context::strand& strand, boost::asio::yield_context& yc); + private: inline AsioTlsStream(UnbufferedAsioTlsStreamParams init) From 007e3fbe7ef08696706df023a2fe44f110fb2ea2 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Mon, 12 Feb 2024 17:02:35 +0100 Subject: [PATCH 113/415] JsonRpcConnection: use AsioTlsStream::GracefulDisconnect() This new helper functions allows deduplicating the timeout handling for `async_shutdown()`. --- lib/remote/jsonrpcconnection.cpp | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/lib/remote/jsonrpcconnection.cpp b/lib/remote/jsonrpcconnection.cpp index cd684af6e..913177a91 100644 --- a/lib/remote/jsonrpcconnection.cpp +++ b/lib/remote/jsonrpcconnection.cpp @@ -225,36 +225,10 @@ void JsonRpcConnection::Disconnect() m_WriterDone.Wait(yc); - /* - * Do not swallow exceptions in a coroutine. - * https://github.com/Icinga/icinga2/issues/7351 - * We must not catch `detail::forced_unwind exception` as - * this is used for unwinding the stack. - * - * Just use the error_code dummy here. - */ - boost::system::error_code ec; - m_CheckLivenessTimer.cancel(); m_HeartbeatTimer.cancel(); - m_Stream->lowest_layer().cancel(ec); - - Timeout::Ptr shutdownTimeout (new Timeout( - m_IoStrand.context(), - m_IoStrand, - boost::posix_time::seconds(10), - [this, keepAlive](asio::yield_context yc) { - boost::system::error_code ec; - m_Stream->lowest_layer().cancel(ec); - } - )); - - m_Stream->next_layer().async_shutdown(yc[ec]); - - shutdownTimeout->Cancel(); - - m_Stream->lowest_layer().shutdown(m_Stream->lowest_layer().shutdown_both, ec); + m_Stream->GracefulDisconnect(m_IoStrand, yc); }); } } From e6d103d0ddd7b22fe3f1f5415d583e851f0327c9 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Mon, 12 Feb 2024 17:03:54 +0100 Subject: [PATCH 114/415] HttpServerConnection: use AsioTlsStream::GracefulDisconnect() This new helper function has proper timeout handling which was missing here. --- lib/remote/httpserverconnection.cpp | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/lib/remote/httpserverconnection.cpp b/lib/remote/httpserverconnection.cpp index bb5831f4a..bb5620c86 100644 --- a/lib/remote/httpserverconnection.cpp +++ b/lib/remote/httpserverconnection.cpp @@ -88,23 +88,9 @@ void HttpServerConnection::Disconnect(boost::asio::yield_context yc) Log(LogInformation, "HttpServerConnection") << "HTTP client disconnected (from " << m_PeerAddress << ")"; - /* - * Do not swallow exceptions in a coroutine. - * https://github.com/Icinga/icinga2/issues/7351 - * We must not catch `detail::forced_unwind exception` as - * this is used for unwinding the stack. - * - * Just use the error_code dummy here. - */ - boost::system::error_code ec; - m_CheckLivenessTimer.cancel(); - m_Stream->lowest_layer().cancel(ec); - - m_Stream->next_layer().async_shutdown(yc[ec]); - - m_Stream->lowest_layer().shutdown(m_Stream->lowest_layer().shutdown_both, ec); + m_Stream->GracefulDisconnect(m_IoStrand, yc); auto listener (ApiListener::GetInstance()); From a506d562ae1eddbb40f5d0e1cc4beca40574e670 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Wed, 13 Nov 2024 17:18:28 +0100 Subject: [PATCH 115/415] Add comment for remaining uses of async_shutdown() why it's safe The reason for introducing AsioTlsStream::GracefulDisconnect() was to handle the TLS shutdown properly with a timeout since it involves a timeout. However, the implementation of this timeout involves spwaning coroutines which are redundant in some cases. This commit adds comments to the remaining calls of async_shutdown() stating why calling it is safe in these places. --- lib/methods/ifwapichecktask.cpp | 2 ++ lib/remote/apilistener.cpp | 3 +++ 2 files changed, 5 insertions(+) diff --git a/lib/methods/ifwapichecktask.cpp b/lib/methods/ifwapichecktask.cpp index 16e2f7e63..9a62444b6 100644 --- a/lib/methods/ifwapichecktask.cpp +++ b/lib/methods/ifwapichecktask.cpp @@ -102,6 +102,8 @@ static void DoIfwNetIo( } { + // Using async_shutdown() instead of AsioTlsStream::GracefulDisconnect() as this whole function + // is already guarded by a timeout based on the check timeout. boost::system::error_code ec; sslConn.async_shutdown(yc[ec]); } diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 201b22865..9c2a489da 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -719,6 +719,9 @@ void ApiListener::NewClientHandlerInternal( // Ignore the error, but do not throw an exception being swallowed at all cost. // https://github.com/Icinga/icinga2/issues/7351 boost::system::error_code ec; + + // Using async_shutdown() instead of AsioTlsStream::GracefulDisconnect() as this whole function + // is already guarded by a timeout based on the connect timeout. sslConn.async_shutdown(yc[ec]); } }); From 3a09cf72d60d5577daa211ea2a5904224ff82eeb Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 4 Dec 2024 11:19:37 +0100 Subject: [PATCH 116/415] DependencyGraph: use ConfigObject*, not Object* This saves dynamic_cast + if() on every item of GetChildren(). --- lib/base/dependencygraph.cpp | 13 ++++++------- lib/base/dependencygraph.hpp | 10 +++++----- lib/base/scriptutils.cpp | 2 +- lib/remote/configobjectutility.cpp | 7 +------ lib/remote/objectqueryhandler.cpp | 8 +------- tools/mkclass/classcompiler.cpp | 24 ++++++++++++------------ 6 files changed, 26 insertions(+), 38 deletions(-) diff --git a/lib/base/dependencygraph.cpp b/lib/base/dependencygraph.cpp index 88cb18194..58f685ce0 100644 --- a/lib/base/dependencygraph.cpp +++ b/lib/base/dependencygraph.cpp @@ -5,15 +5,15 @@ using namespace icinga; std::mutex DependencyGraph::m_Mutex; -std::map > DependencyGraph::m_Dependencies; +std::map> DependencyGraph::m_Dependencies; -void DependencyGraph::AddDependency(Object* child, Object* parent) +void DependencyGraph::AddDependency(ConfigObject* child, ConfigObject* parent) { std::unique_lock lock(m_Mutex); m_Dependencies[parent][child]++; } -void DependencyGraph::RemoveDependency(Object* child, Object* parent) +void DependencyGraph::RemoveDependency(ConfigObject* child, ConfigObject* parent) { std::unique_lock lock(m_Mutex); @@ -32,16 +32,15 @@ void DependencyGraph::RemoveDependency(Object* child, Object* parent) m_Dependencies.erase(parent); } -std::vector DependencyGraph::GetChildren(const Object::Ptr& parent) +std::vector DependencyGraph::GetChildren(const ConfigObject::Ptr& parent) { - std::vector objects; + std::vector objects; std::unique_lock lock(m_Mutex); auto it = m_Dependencies.find(parent.get()); if (it != m_Dependencies.end()) { - typedef std::pair kv_pair; - for (const kv_pair& kv : it->second) { + for (auto& kv : it->second) { objects.emplace_back(kv.first); } } diff --git a/lib/base/dependencygraph.hpp b/lib/base/dependencygraph.hpp index 43dd97c08..6afb08fc5 100644 --- a/lib/base/dependencygraph.hpp +++ b/lib/base/dependencygraph.hpp @@ -4,7 +4,7 @@ #define DEPENDENCYGRAPH_H #include "base/i2-base.hpp" -#include "base/object.hpp" +#include "base/configobject.hpp" #include #include @@ -18,15 +18,15 @@ namespace icinga { class DependencyGraph { public: - static void AddDependency(Object* child, Object* parent); - static void RemoveDependency(Object* child, Object* parent); - static std::vector GetChildren(const Object::Ptr& parent); + static void AddDependency(ConfigObject* child, ConfigObject* parent); + static void RemoveDependency(ConfigObject* child, ConfigObject* parent); + static std::vector GetChildren(const ConfigObject::Ptr& parent); private: DependencyGraph(); static std::mutex m_Mutex; - static std::map > m_Dependencies; + static std::map> m_Dependencies; }; } diff --git a/lib/base/scriptutils.cpp b/lib/base/scriptutils.cpp index 5bf2164a7..3611799ff 100644 --- a/lib/base/scriptutils.cpp +++ b/lib/base/scriptutils.cpp @@ -520,7 +520,7 @@ String ScriptUtils::MsiGetComponentPathShim(const String& component) Array::Ptr ScriptUtils::TrackParents(const Object::Ptr& child) { - return Array::FromVector(DependencyGraph::GetChildren(child)); + return Array::FromVector(DependencyGraph::GetChildren(dynamic_pointer_cast(child))); } double ScriptUtils::Ptr(const Object::Ptr& object) diff --git a/lib/remote/configobjectutility.cpp b/lib/remote/configobjectutility.cpp index 75dcfab93..9d1958050 100644 --- a/lib/remote/configobjectutility.cpp +++ b/lib/remote/configobjectutility.cpp @@ -319,12 +319,7 @@ bool ConfigObjectUtility::DeleteObjectHelper(const ConfigObject::Ptr& object, bo return false; } - for (const Object::Ptr& pobj : parents) { - ConfigObject::Ptr parentObj = dynamic_pointer_cast(pobj); - - if (!parentObj) - continue; - + for (auto& parentObj : parents) { DeleteObjectHelper(parentObj, cascade, errors, diagnosticInformation, cookie); } diff --git a/lib/remote/objectqueryhandler.cpp b/lib/remote/objectqueryhandler.cpp index 4c40ef3ed..8b7313789 100644 --- a/lib/remote/objectqueryhandler.cpp +++ b/lib/remote/objectqueryhandler.cpp @@ -208,13 +208,7 @@ bool ObjectQueryHandler::HandleRequest( Array::Ptr used_by = new Array(); metaAttrs.emplace_back("used_by", used_by); - for (auto& pobj : DependencyGraph::GetChildren(obj)) - { - ConfigObject::Ptr configObj = dynamic_pointer_cast(pobj); - - if (!configObj) - continue; - + for (auto& configObj : DependencyGraph::GetChildren(obj)) { used_by->Add(new Dictionary({ { "type", configObj->GetReflectionType()->GetName() }, { "name", configObj->GetName() } diff --git a/tools/mkclass/classcompiler.cpp b/tools/mkclass/classcompiler.cpp index 6082cbed6..693011369 100644 --- a/tools/mkclass/classcompiler.cpp +++ b/tools/mkclass/classcompiler.cpp @@ -903,13 +903,13 @@ void ClassCompiler::HandleClass(const Klass& klass, const ClassDebugInfo&) m_Impl << "\t" << "if (oldValue) {" << std::endl << "\t\t" << "ObjectLock olock(oldValue);" << std::endl << "\t\t" << "for (const String& ref : oldValue) {" << std::endl - << "\t\t\t" << "DependencyGraph::RemoveDependency(this, ConfigObject::GetObject"; + << "\t\t\tDependencyGraph::RemoveDependency("; /* Ew */ if (field.Type.TypeName == "Zone" && m_Library == "base") - m_Impl << "(\"Zone\", "; + m_Impl << "dynamic_cast(this), ConfigObject::GetObject(\"Zone\", "; else - m_Impl << "<" << field.Type.TypeName << ">("; + m_Impl << "this, ConfigObject::GetObject<" << field.Type.TypeName << ">("; m_Impl << "ref).get());" << std::endl << "\t\t" << "}" << std::endl @@ -917,36 +917,36 @@ void ClassCompiler::HandleClass(const Klass& klass, const ClassDebugInfo&) << "\t" << "if (newValue) {" << std::endl << "\t\t" << "ObjectLock olock(newValue);" << std::endl << "\t\t" << "for (const String& ref : newValue) {" << std::endl - << "\t\t\t" << "DependencyGraph::AddDependency(this, ConfigObject::GetObject"; + << "\t\t\tDependencyGraph::AddDependency("; /* Ew */ if (field.Type.TypeName == "Zone" && m_Library == "base") - m_Impl << "(\"Zone\", "; + m_Impl << "dynamic_cast(this), ConfigObject::GetObject(\"Zone\", "; else - m_Impl << "<" << field.Type.TypeName << ">("; + m_Impl << "this, ConfigObject::GetObject<" << field.Type.TypeName << ">("; m_Impl << "ref).get());" << std::endl << "\t\t" << "}" << std::endl << "\t" << "}" << std::endl; } else { m_Impl << "\t" << "if (!oldValue.IsEmpty())" << std::endl - << "\t\t" << "DependencyGraph::RemoveDependency(this, ConfigObject::GetObject"; + << "\t\tDependencyGraph::RemoveDependency("; /* Ew */ if (field.Type.TypeName == "Zone" && m_Library == "base") - m_Impl << "(\"Zone\", "; + m_Impl << "dynamic_cast(this), ConfigObject::GetObject(\"Zone\", "; else - m_Impl << "<" << field.Type.TypeName << ">("; + m_Impl << "this, ConfigObject::GetObject<" << field.Type.TypeName << ">("; m_Impl << "oldValue).get());" << std::endl << "\t" << "if (!newValue.IsEmpty())" << std::endl - << "\t\t" << "DependencyGraph::AddDependency(this, ConfigObject::GetObject"; + << "\t\tDependencyGraph::AddDependency("; /* Ew */ if (field.Type.TypeName == "Zone" && m_Library == "base") - m_Impl << "(\"Zone\", "; + m_Impl << "dynamic_cast(this), ConfigObject::GetObject(\"Zone\", "; else - m_Impl << "<" << field.Type.TypeName << ">("; + m_Impl << "this, ConfigObject::GetObject<" << field.Type.TypeName << ">("; m_Impl << "newValue).get());" << std::endl; } From d9cbed439a5742e2bdec826eced0575f38ff66ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Wed, 18 Dec 2024 15:30:40 +0100 Subject: [PATCH 117/415] GHA: drop EOL SLES 12.5 --- .github/workflows/rpm.yml | 116 -------------------------------------- 1 file changed, 116 deletions(-) delete mode 100644 .github/workflows/rpm.yml diff --git a/.github/workflows/rpm.yml b/.github/workflows/rpm.yml deleted file mode 100644 index 5cf8b10b0..000000000 --- a/.github/workflows/rpm.yml +++ /dev/null @@ -1,116 +0,0 @@ -name: .rpm - -on: - push: - branches: - - master - - 'support/*' - pull_request: {} - -concurrency: - group: rpm-${{ github.event_name == 'push' && github.sha || github.ref }} - cancel-in-progress: true - -jobs: - rpm: - name: .rpm (${{ matrix.distro.name }}, ${{ matrix.distro.release }}) - - strategy: - fail-fast: false - max-parallel: 1 - matrix: - distro: - - name: sles - release: '12.5' - subscription: true - - runs-on: ubuntu-latest - - steps: - - name: Vars - id: vars - env: - GITLAB_RO_TOKEN: '${{ secrets.GITLAB_RO_TOKEN }}' - run: | - if [ ${{ matrix.distro.subscription }} = true ]; then - if [ "$(tr -d '\n' <<<"$GITLAB_RO_TOKEN" |wc -c)" -eq 0 ]; then - echo '::set-output name=CAN_BUILD::false' - echo '::set-output name=NEED_LOGIN::false' - else - echo '::set-output name=CAN_BUILD::true' - echo '::set-output name=NEED_LOGIN::true' - fi - else - echo '::set-output name=CAN_BUILD::true' - echo '::set-output name=NEED_LOGIN::false' - fi - - - name: Checkout HEAD - if: "steps.vars.outputs.CAN_BUILD == 'true'" - uses: actions/checkout@v1 - - - name: Login - if: "steps.vars.outputs.NEED_LOGIN == 'true'" - env: - GITLAB_RO_TOKEN: '${{ secrets.GITLAB_RO_TOKEN }}' - run: | - docker login registry.icinga.com -u github-actions --password-stdin <<<"$GITLAB_RO_TOKEN" - - - name: rpm-icinga2 - if: "steps.vars.outputs.CAN_BUILD == 'true' && !matrix.distro.subscription" - run: | - set -exo pipefail - git clone https://git.icinga.com/packaging/rpm-icinga2.git - chmod o+w rpm-icinga2 - - - name: subscription-rpm-icinga2 - if: "steps.vars.outputs.CAN_BUILD == 'true' && matrix.distro.subscription" - env: - GITLAB_RO_TOKEN: '${{ secrets.GITLAB_RO_TOKEN }}' - run: | - set -exo pipefail - git config --global credential.helper store - cat <~/.git-credentials - https://github-actions:${GITLAB_RO_TOKEN}@git.icinga.com - EOF - git clone https://git.icinga.com/packaging/subscription-rpm-icinga2.git rpm-icinga2 - chmod o+w rpm-icinga2 - - - name: Restore/backup ccache - if: "steps.vars.outputs.CAN_BUILD == 'true'" - id: ccache - uses: actions/cache@v1 - with: - path: rpm-icinga2/ccache - key: |- - ${{ matrix.distro.name }}/${{ matrix.distro.release }}-ccache-${{ hashFiles('rpm-icinga2/ccache') }} - - - name: Binary - if: "steps.vars.outputs.CAN_BUILD == 'true'" - run: | - set -exo pipefail - git checkout -B master - if [ -e rpm-icinga2/ccache ]; then - chmod -R o+w rpm-icinga2/ccache - fi - docker run --rm \ - -v "$(pwd)/rpm-icinga2:/rpm-icinga2" \ - -v "$(pwd)/.git:/icinga2.git:ro" \ - -w /rpm-icinga2 \ - -e ICINGA_BUILD_PROJECT=icinga2 \ - -e ICINGA_BUILD_TYPE=snapshot \ - -e UPSTREAM_GIT_URL=file:///icinga2.git \ - registry.icinga.com/build-docker/${{ matrix.distro.name }}/${{ matrix.distro.release }} \ - icinga-build-package - - - name: Test - if: "steps.vars.outputs.CAN_BUILD == 'true'" - run: | - set -exo pipefail - docker run --rm \ - -v "$(pwd)/rpm-icinga2:/rpm-icinga2" \ - -w /rpm-icinga2 \ - -e ICINGA_BUILD_PROJECT=icinga2 \ - -e ICINGA_BUILD_TYPE=snapshot \ - registry.icinga.com/build-docker/${{ matrix.distro.name }}/${{ matrix.distro.release }} \ - icinga-build-test From 929deffb4bb433313846b9ea48a3a11291c7b93d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Wed, 18 Dec 2024 15:31:56 +0100 Subject: [PATCH 118/415] GHA: drop EOL SLES 15.3 --- .github/workflows/linux.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 039c460d3..3aadf2e4d 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -37,7 +37,6 @@ jobs: # As openSUSE is much easier to deploy, we test it despite the fact that we don't necessarily # support individual versions of openSUSE as long as their SLES counterparts. # I.e., remove any opensuse/leap:* line below only if we stopped packaging both openSUSE and SLES! - - opensuse/leap:15.3 - opensuse/leap:15.4 - opensuse/leap:15.5 - opensuse/leap:15.6 From f098810892ed9d4094c3dda7e70710da413602df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Wed, 18 Dec 2024 15:32:37 +0100 Subject: [PATCH 119/415] GHA: drop EOL SUSE 15.4 --- .github/workflows/linux.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 3aadf2e4d..4d5454830 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -37,7 +37,6 @@ jobs: # As openSUSE is much easier to deploy, we test it despite the fact that we don't necessarily # support individual versions of openSUSE as long as their SLES counterparts. # I.e., remove any opensuse/leap:* line below only if we stopped packaging both openSUSE and SLES! - - opensuse/leap:15.4 - opensuse/leap:15.5 - opensuse/leap:15.6 From b2288d2925e8fc3893907cbbad6f79289e58c554 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Thu, 2 Jan 2025 15:26:31 +0100 Subject: [PATCH 120/415] GHA: also test SLES, not just openSUSE They may be similar, but SLES isn't that hard to deploy. --- .github/workflows/linux.bash | 2 +- .github/workflows/linux.yml | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/linux.bash b/.github/workflows/linux.bash index 51b978f5c..232227cb9 100755 --- a/.github/workflows/linux.bash +++ b/.github/workflows/linux.bash @@ -44,7 +44,7 @@ case "$DISTRO" in {boost,libedit,mariadb,ncurses,openssl,postgresql,systemd}-devel ;; - opensuse/*) + *suse*) zypper in -y bison ccache cmake flex gcc-c++ ninja {lib{edit,mariadb,openssl},ncurses,postgresql,systemd}-devel \ libboost_{context,coroutine,filesystem,iostreams,program_options,regex,system,test,thread}-devel ;; diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 4d5454830..1b1b1ac89 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -33,10 +33,6 @@ jobs: - fedora:40 - fedora:41 - # openSUSE Leap is close enough to SLES to test just one of them. - # As openSUSE is much easier to deploy, we test it despite the fact that we don't necessarily - # support individual versions of openSUSE as long as their SLES counterparts. - # I.e., remove any opensuse/leap:* line below only if we stopped packaging both openSUSE and SLES! - opensuse/leap:15.5 - opensuse/leap:15.6 @@ -45,6 +41,9 @@ jobs: - rockylinux:8 - rockylinux:9 + - registry.suse.com/suse/sle15:15.5 + - registry.suse.com/suse/sle15:15.6 + - ubuntu:20.04 - ubuntu:22.04 - ubuntu:24.04 From 7ea0f5969fcc0a66e3c81a88786b3a0e83ebf5de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Thu, 2 Jan 2025 15:35:29 +0100 Subject: [PATCH 121/415] GHA: Amazon Linux: fix broken link to Boost tarball --- .github/workflows/linux.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linux.bash b/.github/workflows/linux.bash index 51b978f5c..c6db5539d 100755 --- a/.github/workflows/linux.bash +++ b/.github/workflows/linux.bash @@ -13,7 +13,7 @@ case "$DISTRO" in {libedit,mariadb,ncurses,openssl,postgresql,systemd}-devel yum install -y bzip2 tar wget - wget https://boostorg.jfrog.io/artifactory/main/release/1.69.0/source/boost_1_69_0.tar.bz2 + wget https://archives.boost.io/release/1.69.0/source/boost_1_69_0.tar.bz2 tar -xjf boost_1_69_0.tar.bz2 ( From 015374e69db2a8d37d3c8f88a5c871329e1b81e8 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 4 Dec 2024 08:03:01 +0100 Subject: [PATCH 122/415] DependencyGraph: Allow lookups by parent & child dependencies --- lib/base/dependencygraph.cpp | 68 +++++++++++++++++++++----------- lib/base/dependencygraph.hpp | 75 +++++++++++++++++++++++++++++++++++- 2 files changed, 119 insertions(+), 24 deletions(-) diff --git a/lib/base/dependencygraph.cpp b/lib/base/dependencygraph.cpp index 58f685ce0..a9d0dbb95 100644 --- a/lib/base/dependencygraph.cpp +++ b/lib/base/dependencygraph.cpp @@ -5,45 +5,69 @@ using namespace icinga; std::mutex DependencyGraph::m_Mutex; -std::map> DependencyGraph::m_Dependencies; +DependencyGraph::DependencyMap DependencyGraph::m_Dependencies; void DependencyGraph::AddDependency(ConfigObject* child, ConfigObject* parent) { std::unique_lock lock(m_Mutex); - m_Dependencies[parent][child]++; + if (auto [it, inserted] = m_Dependencies.insert(Edge(parent, child)); !inserted) { + m_Dependencies.modify(it, [](Edge& e) { e.count++; }); + } } void DependencyGraph::RemoveDependency(ConfigObject* child, ConfigObject* parent) { std::unique_lock lock(m_Mutex); - auto& refs = m_Dependencies[parent]; - auto it = refs.find(child); + if (auto it(m_Dependencies.find(Edge(parent, child))); it != m_Dependencies.end()) { + // A number <= 1 means, this isn't referenced by anyone and should be erased from the container. + if (it->count == 1) { + m_Dependencies.erase(it); + return; + } - if (it == refs.end()) - return; - - it->second--; - - if (it->second == 0) - refs.erase(it); - - if (refs.empty()) - m_Dependencies.erase(parent); + // Otherwise, each remove operation will should decrement this by 1 till it reaches <= 1 + // and causes the edge to completely be erased from the container. + m_Dependencies.modify(it, [](Edge& e) { e.count--; }); + } } +/** + * Returns all the parent objects of the given child object. + * + * @param child The child object. + * + * @returns A list of the parent objects. + */ +std::vector DependencyGraph::GetParents(const ConfigObject::Ptr& child) +{ + std::vector objects; + + std::unique_lock lock(m_Mutex); + auto [begin, end] = m_Dependencies.get<2>().equal_range(child.get()); + std::transform(begin, end, std::back_inserter(objects), [](const Edge& edge) { + return edge.parent; + }); + + return objects; +} + +/** + * Returns all the dependent objects of the given parent object. + * + * @param parent The parent object. + * + * @returns A list of the dependent objects. + */ std::vector DependencyGraph::GetChildren(const ConfigObject::Ptr& parent) { std::vector objects; - std::unique_lock lock(m_Mutex); - auto it = m_Dependencies.find(parent.get()); - - if (it != m_Dependencies.end()) { - for (auto& kv : it->second) { - objects.emplace_back(kv.first); - } - } + std::unique_lock lock(m_Mutex); + auto [begin, end] = m_Dependencies.get<1>().equal_range(parent.get()); + std::transform(begin, end, std::back_inserter(objects), [](const Edge& edge) { + return edge.child; + }); return objects; } diff --git a/lib/base/dependencygraph.hpp b/lib/base/dependencygraph.hpp index 6afb08fc5..ef1a2a4a6 100644 --- a/lib/base/dependencygraph.hpp +++ b/lib/base/dependencygraph.hpp @@ -5,7 +5,9 @@ #include "base/i2-base.hpp" #include "base/configobject.hpp" -#include +#include +#include +#include #include namespace icinga { @@ -20,13 +22,82 @@ class DependencyGraph public: static void AddDependency(ConfigObject* child, ConfigObject* parent); static void RemoveDependency(ConfigObject* child, ConfigObject* parent); + static std::vector GetParents(const ConfigObject::Ptr& child); static std::vector GetChildren(const ConfigObject::Ptr& parent); private: DependencyGraph(); + /** + * Represents an undirected dependency edge between two objects. + * + * It allows to traverse the graph in both directions, i.e. from parent to child and vice versa. + */ + struct Edge + { + ConfigObject* parent; // The parent object of the child one. + ConfigObject* child; // The dependent object of the parent. + // Counter for the number of parent <-> child edges to allow duplicates. + int count; + + Edge(ConfigObject* parent, ConfigObject* child, int count = 1): parent(parent), child(child), count(count) + { + } + + struct Hash + { + /** + * Generates a unique hash of the given Edge object. + * + * Note, the hash value is generated only by combining the hash values of the parent and child pointers. + * + * @param edge The Edge object to be hashed. + * + * @return size_t The resulting hash value of the given object. + */ + size_t operator()(const Edge& edge) const + { + size_t seed = 0; + boost::hash_combine(seed, edge.parent); + boost::hash_combine(seed, edge.child); + + return seed; + } + }; + + struct Equal + { + /** + * Compares whether the two Edge objects contain the same parent and child pointers. + * + * Note, the member property count is not taken into account for equality checks. + * + * @param a The first Edge object to compare. + * @param b The second Edge object to compare. + * + * @return bool Returns true if the two objects are equal, false otherwise. + */ + bool operator()(const Edge& a, const Edge& b) const + { + return a.parent == b.parent && a.child == b.child; + } + }; + }; + + using DependencyMap = boost::multi_index_container< + Edge, // The value type we want to sore in the container. + boost::multi_index::indexed_by< + // The first indexer is used for lookups by the Edge from child to parent, thus it + // needs its own hash function and comparison predicate. + boost::multi_index::hashed_unique, Edge::Hash, Edge::Equal>, + // These two indexers are used for lookups by the parent and child pointers. + boost::multi_index::hashed_non_unique>, + boost::multi_index::hashed_non_unique> + > + >; + static std::mutex m_Mutex; - static std::map> m_Dependencies; + static DependencyMap m_Dependencies; }; } From ff0e12e6ac71ffd8f93db7330551e386c0c7a9d7 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 16 Sep 2024 09:20:43 +0200 Subject: [PATCH 123/415] ApiListener: Sync runtime configs in order --- lib/remote/apilistener-configsync.cpp | 60 +++++++++++++++++++++------ lib/remote/apilistener.hpp | 2 + 2 files changed, 49 insertions(+), 13 deletions(-) diff --git a/lib/remote/apilistener-configsync.cpp b/lib/remote/apilistener-configsync.cpp index 04436ad8b..6d97258ec 100644 --- a/lib/remote/apilistener-configsync.cpp +++ b/lib/remote/apilistener-configsync.cpp @@ -5,11 +5,13 @@ #include "remote/configobjectutility.hpp" #include "remote/jsonrpc.hpp" #include "base/configtype.hpp" -#include "base/json.hpp" #include "base/convert.hpp" +#include "base/dependencygraph.hpp" +#include "base/json.hpp" #include "config/vmops.hpp" #include "remote/configobjectslock.hpp" #include +#include using namespace icinga; @@ -393,6 +395,40 @@ void ApiListener::UpdateConfigObject(const ConfigObject::Ptr& object, const Mess } } +/** + * Syncs the specified object and its direct and indirect parents to the provided client + * in topological order of their dependency graph recursively. + * + * Objects that the client does not have access to are skipped without going through their dependency graph. + * + * Please do not use this method to forward remote generated cluster updates; it should only be used to + * send local updates to that specific non-nullptr client. + * + * @param object The config object you want to sync. + * @param azone The zone of the client you want to send the update to. + * @param client The JsonRpc client you send the update to. + * @param syncedObjects Used to cache the already synced objects. + */ +void ApiListener::UpdateConfigObjectWithParents(const ConfigObject::Ptr& object, const Zone::Ptr& azone, + const JsonRpcConnection::Ptr& client, std::unordered_set& syncedObjects) +{ + if (syncedObjects.find(object.get()) != syncedObjects.end()) { + return; + } + + /* don't sync objects for non-matching parent-child zones */ + if (!azone->CanAccessObject(object)) { + return; + } + syncedObjects.emplace(object.get()); + + for (const auto& parent : DependencyGraph::GetParents(object)) { + UpdateConfigObjectWithParents(parent, azone, client, syncedObjects); + } + + /* send the config object to the connected client */ + UpdateConfigObject(object, nullptr, client); +} void ApiListener::DeleteConfigObject(const ConfigObject::Ptr& object, const MessageOrigin::Ptr& origin, const JsonRpcConnection::Ptr& client) @@ -454,19 +490,17 @@ void ApiListener::SendRuntimeConfigObjects(const JsonRpcConnection::Ptr& aclient Log(LogInformation, "ApiListener") << "Syncing runtime objects to endpoint '" << endpoint->GetName() << "'."; + std::unordered_set syncedObjects; for (const Type::Ptr& type : Type::GetAllTypes()) { - auto *dtype = dynamic_cast(type.get()); - - if (!dtype) - continue; - - for (const ConfigObject::Ptr& object : dtype->GetObjects()) { - /* don't sync objects for non-matching parent-child zones */ - if (!azone->CanAccessObject(object)) - continue; - - /* send the config object to the connected client */ - UpdateConfigObject(object, nullptr, aclient); + if (auto *ctype = dynamic_cast(type.get())) { + for (const auto& object : ctype->GetObjects()) { + // All objects must be synced sorted by their dependency graph. + // Otherwise, downtimes/comments etc. might get synced before their respective Checkables, which will + // result in comments and downtimes being ignored by the other endpoint since it does not yet know + // about their checkables. Given that the runtime config updates event does not trigger a reload on the + // remote endpoint, these objects won't be synced again until the next reload. + UpdateConfigObjectWithParents(object, azone, aclient, syncedObjects); + } } } diff --git a/lib/remote/apilistener.hpp b/lib/remote/apilistener.hpp index fced0a8af..eae1fa03e 100644 --- a/lib/remote/apilistener.hpp +++ b/lib/remote/apilistener.hpp @@ -247,6 +247,8 @@ private: /* configsync */ void UpdateConfigObject(const ConfigObject::Ptr& object, const MessageOrigin::Ptr& origin, const JsonRpcConnection::Ptr& client = nullptr); + void UpdateConfigObjectWithParents(const ConfigObject::Ptr& object, const Zone::Ptr& azone, + const JsonRpcConnection::Ptr& client, std::unordered_set& syncedObjects); void DeleteConfigObject(const ConfigObject::Ptr& object, const MessageOrigin::Ptr& origin, const JsonRpcConnection::Ptr& client = nullptr); void SendRuntimeConfigObjects(const JsonRpcConnection::Ptr& aclient); From cf125dd8d5271951f32b327ef1dd383df01fd70c Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Thu, 19 Dec 2024 12:24:26 +0100 Subject: [PATCH 124/415] Simplify `DependencyGraph:RemoveDependency()` method --- lib/base/dependencygraph.cpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/base/dependencygraph.cpp b/lib/base/dependencygraph.cpp index a9d0dbb95..06446cfc2 100644 --- a/lib/base/dependencygraph.cpp +++ b/lib/base/dependencygraph.cpp @@ -20,15 +20,14 @@ void DependencyGraph::RemoveDependency(ConfigObject* child, ConfigObject* parent std::unique_lock lock(m_Mutex); if (auto it(m_Dependencies.find(Edge(parent, child))); it != m_Dependencies.end()) { - // A number <= 1 means, this isn't referenced by anyone and should be erased from the container. - if (it->count == 1) { + if (it->count > 1) { + // Remove a duplicate edge from child to node, i.e. decrement the corresponding counter. + m_Dependencies.modify(it, [](Edge& e) { e.count--; }); + } else { + // Remove the last edge from child to node (decrementing the counter would set it to 0), + // thus remove that connection from the data structure completely. m_Dependencies.erase(it); - return; } - - // Otherwise, each remove operation will should decrement this by 1 till it reaches <= 1 - // and causes the edge to completely be erased from the container. - m_Dependencies.modify(it, [](Edge& e) { e.count--; }); } } From 120c89af55bf2b9c7829555f6aa76f7d8c85a51e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Tue, 7 Jan 2025 13:06:58 +0100 Subject: [PATCH 125/415] release.md: don't update doc/21-development.md The exact Boost version noted there for Windows doesn't matter. --- .github/ISSUE_TEMPLATE/release.md | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/release.md b/.github/ISSUE_TEMPLATE/release.md index f0310bef7..def3b2ef3 100644 --- a/.github/ISSUE_TEMPLATE/release.md +++ b/.github/ISSUE_TEMPLATE/release.md @@ -35,7 +35,6 @@ https://packages.icinga.com/windows/dependencies/, e.g.: ### Update Build Server, CI/CD and Documentation -* [doc/21-development.md](doc/21-development.md) * [doc/win-dev.ps1](doc/win-dev.ps1) (also affects CI/CD) * [tools/win32/configure.ps1](tools/win32/configure.ps1) * [tools/win32/configure-dev.ps1](tools/win32/configure-dev.ps1) From 92ab913226ea8fdb3686e1c7001f82dbae65517b Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 25 Nov 2024 17:58:58 +0100 Subject: [PATCH 126/415] Timeout#Timeout(): don't pass yield_context to callback It's not used. Also, the callback shall run completely at once. This ensures that it won't (continue to) run once another coroutine on the strand calls Timeout#Cancel(). --- lib/base/io-engine.hpp | 2 +- lib/base/tlsstream.cpp | 2 +- lib/icingadb/redisconnection.hpp | 2 +- lib/methods/ifwapichecktask.cpp | 2 +- lib/remote/apilistener.cpp | 6 +++--- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/base/io-engine.hpp b/lib/base/io-engine.hpp index 326f04fdc..83ac9aaab 100644 --- a/lib/base/io-engine.hpp +++ b/lib/base/io-engine.hpp @@ -199,7 +199,7 @@ public: } auto f (onTimeout); - f(std::move(yc)); + f(); }); } diff --git a/lib/base/tlsstream.cpp b/lib/base/tlsstream.cpp index ed8005837..c7f2884f4 100644 --- a/lib/base/tlsstream.cpp +++ b/lib/base/tlsstream.cpp @@ -141,7 +141,7 @@ void AsioTlsStream::GracefulDisconnect(boost::asio::io_context::strand& strand, { Timeout::Ptr shutdownTimeout(new Timeout(strand.context(), strand, boost::posix_time::seconds(10), - [this](boost::asio::yield_context yc) { + [this] { // Forcefully terminate the connection if async_shutdown() blocked more than 10 seconds. ForceDisconnect(); } diff --git a/lib/icingadb/redisconnection.hpp b/lib/icingadb/redisconnection.hpp index fecd236f9..04c663585 100644 --- a/lib/icingadb/redisconnection.hpp +++ b/lib/icingadb/redisconnection.hpp @@ -520,7 +520,7 @@ Timeout::Ptr RedisConnection::MakeTimeout(StreamPtr& stream) m_Strand.context(), m_Strand, boost::posix_time::microseconds(intmax_t(m_ConnectTimeout * 1000000)), - [keepAlive, stream](boost::asio::yield_context yc) { + [keepAlive, stream] { boost::system::error_code ec; stream->lowest_layer().cancel(ec); } diff --git a/lib/methods/ifwapichecktask.cpp b/lib/methods/ifwapichecktask.cpp index 9a62444b6..d5148623a 100644 --- a/lib/methods/ifwapichecktask.cpp +++ b/lib/methods/ifwapichecktask.cpp @@ -457,7 +457,7 @@ void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes *strand, [strand, checkable, cr, psCommand, psHost, expectedSan, psPort, conn, req, checkTimeout, reportResult = std::move(reportResult)](asio::yield_context yc) { Timeout::Ptr timeout = new Timeout(strand->context(), *strand, boost::posix_time::microseconds(int64_t(checkTimeout * 1e6)), - [&conn, &checkable](boost::asio::yield_context yc) { + [&conn, &checkable] { Log(LogNotice, "IfwApiCheckTask") << "Timeout while checking " << checkable->GetReflectionType()->GetName() << " '" << checkable->GetName() << "', cancelling attempt"; diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 9c2a489da..601367e49 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -535,7 +535,7 @@ void ApiListener::ListenerCoroutineProc(boost::asio::yield_context yc, const Sha IoEngine::SpawnCoroutine(*strand, [this, strand, sslConn, remoteEndpoint](asio::yield_context yc) { Timeout::Ptr timeout(new Timeout(strand->context(), *strand, boost::posix_time::microseconds(int64_t(GetConnectTimeout() * 1e6)), - [sslConn, remoteEndpoint](asio::yield_context yc) { + [sslConn, remoteEndpoint] { Log(LogWarning, "ApiListener") << "Timeout while processing incoming connection from " << remoteEndpoint; @@ -586,7 +586,7 @@ void ApiListener::AddConnection(const Endpoint::Ptr& endpoint) lock.unlock(); Timeout::Ptr timeout(new Timeout(strand->context(), *strand, boost::posix_time::microseconds(int64_t(GetConnectTimeout() * 1e6)), - [sslConn, endpoint, host, port](asio::yield_context yc) { + [sslConn, endpoint, host, port] { Log(LogCritical, "ApiListener") << "Timeout while reconnecting to endpoint '" << endpoint->GetName() << "' via host '" << host << "' and port '" << port << "', cancelling attempt"; @@ -687,7 +687,7 @@ void ApiListener::NewClientHandlerInternal( strand->context(), *strand, boost::posix_time::microseconds(intmax_t(Configuration::TlsHandshakeTimeout * 1000000)), - [strand, client](asio::yield_context yc) { + [strand, client] { boost::system::error_code ec; client->lowest_layer().cancel(ec); } From faaeb4eb2e8e4514a7476307419a3d51413593e9 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 26 Nov 2024 15:04:27 +0100 Subject: [PATCH 127/415] Timeout: use a plain callback, not an unnecessary coroutine --- lib/base/io-engine.cpp | 2 -- lib/base/io-engine.hpp | 33 +++++---------------------------- 2 files changed, 5 insertions(+), 30 deletions(-) diff --git a/lib/base/io-engine.cpp b/lib/base/io-engine.cpp index 26125feb3..2727236ad 100644 --- a/lib/base/io-engine.cpp +++ b/lib/base/io-engine.cpp @@ -148,8 +148,6 @@ void AsioConditionVariable::Wait(boost::asio::yield_context yc) void Timeout::Cancel() { - m_Cancelled.store(true); - boost::system::error_code ec; m_Timer.cancel(ec); } diff --git a/lib/base/io-engine.hpp b/lib/base/io-engine.hpp index 83ac9aaab..81efc1c96 100644 --- a/lib/base/io-engine.hpp +++ b/lib/base/io-engine.hpp @@ -172,42 +172,19 @@ public: template Timeout(boost::asio::io_context& io, Executor& executor, TimeoutFromNow timeoutFromNow, OnTimeout onTimeout) - : m_Timer(io) + : m_Timer(io, timeoutFromNow) { - Ptr keepAlive (this); - - m_Cancelled.store(false); - m_Timer.expires_from_now(std::move(timeoutFromNow)); - - IoEngine::SpawnCoroutine(executor, [this, keepAlive, onTimeout](boost::asio::yield_context yc) { - if (m_Cancelled.load()) { - return; + m_Timer.async_wait(boost::asio::bind_executor(executor, [onTimeout = std::move(onTimeout)](boost::system::error_code ec) { + if (!ec) { + onTimeout(); } - - { - boost::system::error_code ec; - - m_Timer.async_wait(yc[ec]); - - if (ec) { - return; - } - } - - if (m_Cancelled.load()) { - return; - } - - auto f (onTimeout); - f(); - }); + })); } void Cancel(); private: boost::asio::deadline_timer m_Timer; - std::atomic m_Cancelled; }; } From 8cdbea303baec4f0c635efd6e440e4230f3755f4 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 26 Nov 2024 15:30:06 +0100 Subject: [PATCH 128/415] Test Timeout --- test/CMakeLists.txt | 6 ++ test/base-io-engine.cpp | 143 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 test/base-io-engine.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index dd9724f0b..a255178da 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -62,6 +62,7 @@ set(base_test_SOURCES base-convert.cpp base-dictionary.cpp base-fifo.cpp + base-io-engine.cpp base-json.cpp base-match.cpp base-netstring.cpp @@ -128,6 +129,11 @@ add_boost_test(base base_dictionary/keys_ordered base_fifo/construct base_fifo/io + base_io_engine/timeout_run + base_io_engine/timeout_cancelled + base_io_engine/timeout_scope + base_io_engine/timeout_due_cancelled + base_io_engine/timeout_due_scope base_json/encode base_json/decode base_json/invalid1 diff --git a/test/base-io-engine.cpp b/test/base-io-engine.cpp new file mode 100644 index 000000000..2261bab91 --- /dev/null +++ b/test/base-io-engine.cpp @@ -0,0 +1,143 @@ +/* Icinga 2 | (c) 2024 Icinga GmbH | GPLv2+ */ + +#include "base/io-engine.hpp" +#include "base/utility.hpp" +#include +#include +#include + +using namespace icinga; + +BOOST_AUTO_TEST_SUITE(base_io_engine) + +BOOST_AUTO_TEST_CASE(timeout_run) +{ + boost::asio::io_context io; + boost::asio::io_context::strand strand (io); + int called = 0; + + boost::asio::spawn(strand, [&](boost::asio::yield_context yc) { + boost::asio::deadline_timer timer (io); + + Timeout::Ptr timeout = new Timeout(io, strand, boost::posix_time::millisec(300), [&called] { ++called; }); + BOOST_CHECK_EQUAL(called, 0); + + timer.expires_from_now(boost::posix_time::millisec(200)); + timer.async_wait(yc); + BOOST_CHECK_EQUAL(called, 0); + + timer.expires_from_now(boost::posix_time::millisec(200)); + timer.async_wait(yc); + }); + + io.run(); + BOOST_CHECK_EQUAL(called, 1); +} + +BOOST_AUTO_TEST_CASE(timeout_cancelled) +{ + boost::asio::io_context io; + boost::asio::io_context::strand strand (io); + int called = 0; + + boost::asio::spawn(strand, [&](boost::asio::yield_context yc) { + boost::asio::deadline_timer timer (io); + Timeout::Ptr timeout = new Timeout(io, strand, boost::posix_time::millisec(300), [&called] { ++called; }); + + timer.expires_from_now(boost::posix_time::millisec(200)); + timer.async_wait(yc); + + timeout->Cancel(); + BOOST_CHECK_EQUAL(called, 0); + + timer.expires_from_now(boost::posix_time::millisec(200)); + timer.async_wait(yc); + }); + + io.run(); + BOOST_CHECK_EQUAL(called, 0); +} + +BOOST_AUTO_TEST_CASE(timeout_scope) +{ + boost::asio::io_context io; + boost::asio::io_context::strand strand (io); + int called = 0; + + boost::asio::spawn(strand, [&](boost::asio::yield_context yc) { + boost::asio::deadline_timer timer (io); + + { + Timeout::Ptr timeout = new Timeout(io, strand, boost::posix_time::millisec(300), [&called] { ++called; }); + + timer.expires_from_now(boost::posix_time::millisec(200)); + timer.async_wait(yc); + } + + BOOST_CHECK_EQUAL(called, 0); + + timer.expires_from_now(boost::posix_time::millisec(200)); + timer.async_wait(yc); + }); + + io.run(); + BOOST_CHECK_EQUAL(called, 0); +} + +BOOST_AUTO_TEST_CASE(timeout_due_cancelled) +{ + boost::asio::io_context io; + boost::asio::io_context::strand strand (io); + int called = 0; + + boost::asio::spawn(strand, [&](boost::asio::yield_context yc) { + boost::asio::deadline_timer timer (io); + Timeout::Ptr timeout = new Timeout(io, strand, boost::posix_time::millisec(300), [&called] { ++called; }); + + // Give the timeout enough time to become due while blocking its strand to prevent it from actually running... + Utility::Sleep(0.4); + + BOOST_CHECK_EQUAL(called, 0); + + // ... so that this shall still work: + timeout->Cancel(); + + BOOST_CHECK_EQUAL(called, 0); + + timer.expires_from_now(boost::posix_time::millisec(100)); + timer.async_wait(yc); + }); + + io.run(); + BOOST_CHECK_EQUAL(called, 0); +} + +BOOST_AUTO_TEST_CASE(timeout_due_scope) +{ + boost::asio::io_context io; + boost::asio::io_context::strand strand (io); + int called = 0; + + boost::asio::spawn(strand, [&](boost::asio::yield_context yc) { + boost::asio::deadline_timer timer (io); + + { + Timeout::Ptr timeout = new Timeout(io, strand, boost::posix_time::millisec(300), [&called] { ++called; }); + + // Give the timeout enough time to become due while blocking its strand to prevent it from actually running... + Utility::Sleep(0.4); + + BOOST_CHECK_EQUAL(called, 0); + } // ... so that Timeout#~Timeout() shall still work here. + + BOOST_CHECK_EQUAL(called, 0); + + timer.expires_from_now(boost::posix_time::millisec(100)); + timer.async_wait(yc); + }); + + io.run(); + BOOST_CHECK_EQUAL(called, 0); +} + +BOOST_AUTO_TEST_SUITE_END() From d2285bcf0e4648233a93d76777fbe26fcbf20d76 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 28 Nov 2024 16:31:18 +0100 Subject: [PATCH 129/415] While using Timeout, don't unnecessarily keep the strand alive via smart pointer --- lib/icingadb/redisconnection.hpp | 4 +--- lib/remote/apilistener.cpp | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/icingadb/redisconnection.hpp b/lib/icingadb/redisconnection.hpp index 04c663585..84be22dba 100644 --- a/lib/icingadb/redisconnection.hpp +++ b/lib/icingadb/redisconnection.hpp @@ -514,13 +514,11 @@ void RedisConnection::Handshake(StreamPtr& strm, boost::asio::yield_context& yc) template Timeout::Ptr RedisConnection::MakeTimeout(StreamPtr& stream) { - Ptr keepAlive (this); - return new Timeout( m_Strand.context(), m_Strand, boost::posix_time::microseconds(intmax_t(m_ConnectTimeout * 1000000)), - [keepAlive, stream] { + [stream] { boost::system::error_code ec; stream->lowest_layer().cancel(ec); } diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 601367e49..913470bd4 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -687,7 +687,7 @@ void ApiListener::NewClientHandlerInternal( strand->context(), *strand, boost::posix_time::microseconds(intmax_t(Configuration::TlsHandshakeTimeout * 1000000)), - [strand, client] { + [client] { boost::system::error_code ec; client->lowest_layer().cancel(ec); } From cb516493638ec9713b8f49a196f7571fe74baca6 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 28 Nov 2024 16:03:27 +0100 Subject: [PATCH 130/415] Timeout#Timeout(): drop unnecessary template parameters --- lib/base/io-engine.hpp | 14 +++++++++----- lib/base/tlsstream.cpp | 2 +- lib/icingadb/redisconnection.hpp | 1 - lib/methods/ifwapichecktask.cpp | 2 +- lib/remote/apilistener.cpp | 5 ++--- test/base-io-engine.cpp | 10 +++++----- 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/lib/base/io-engine.hpp b/lib/base/io-engine.hpp index 81efc1c96..a030d2044 100644 --- a/lib/base/io-engine.hpp +++ b/lib/base/io-engine.hpp @@ -3,6 +3,7 @@ #ifndef IO_ENGINE_H #define IO_ENGINE_H +#include "base/debug.hpp" #include "base/exception.hpp" #include "base/lazy-init.hpp" #include "base/logger.hpp" @@ -169,12 +170,15 @@ class Timeout : public SharedObject { public: DECLARE_PTR_TYPEDEFS(Timeout); + using Timer = boost::asio::deadline_timer; - template - Timeout(boost::asio::io_context& io, Executor& executor, TimeoutFromNow timeoutFromNow, OnTimeout onTimeout) - : m_Timer(io, timeoutFromNow) + template + Timeout(boost::asio::io_context::strand& strand, const Timer::duration_type& timeoutFromNow, OnTimeout onTimeout) + : m_Timer(strand.context(), timeoutFromNow) { - m_Timer.async_wait(boost::asio::bind_executor(executor, [onTimeout = std::move(onTimeout)](boost::system::error_code ec) { + VERIFY(strand.running_in_this_thread()); + + m_Timer.async_wait(boost::asio::bind_executor(strand, [onTimeout = std::move(onTimeout)](boost::system::error_code ec) { if (!ec) { onTimeout(); } @@ -184,7 +188,7 @@ public: void Cancel(); private: - boost::asio::deadline_timer m_Timer; + Timer m_Timer; }; } diff --git a/lib/base/tlsstream.cpp b/lib/base/tlsstream.cpp index c7f2884f4..d1153f2d4 100644 --- a/lib/base/tlsstream.cpp +++ b/lib/base/tlsstream.cpp @@ -140,7 +140,7 @@ void AsioTlsStream::GracefulDisconnect(boost::asio::io_context::strand& strand, } { - Timeout::Ptr shutdownTimeout(new Timeout(strand.context(), strand, boost::posix_time::seconds(10), + Timeout::Ptr shutdownTimeout(new Timeout(strand, boost::posix_time::seconds(10), [this] { // Forcefully terminate the connection if async_shutdown() blocked more than 10 seconds. ForceDisconnect(); diff --git a/lib/icingadb/redisconnection.hpp b/lib/icingadb/redisconnection.hpp index 84be22dba..f73c1fbdf 100644 --- a/lib/icingadb/redisconnection.hpp +++ b/lib/icingadb/redisconnection.hpp @@ -515,7 +515,6 @@ template Timeout::Ptr RedisConnection::MakeTimeout(StreamPtr& stream) { return new Timeout( - m_Strand.context(), m_Strand, boost::posix_time::microseconds(intmax_t(m_ConnectTimeout * 1000000)), [stream] { diff --git a/lib/methods/ifwapichecktask.cpp b/lib/methods/ifwapichecktask.cpp index d5148623a..ad19507e7 100644 --- a/lib/methods/ifwapichecktask.cpp +++ b/lib/methods/ifwapichecktask.cpp @@ -456,7 +456,7 @@ void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes IoEngine::SpawnCoroutine( *strand, [strand, checkable, cr, psCommand, psHost, expectedSan, psPort, conn, req, checkTimeout, reportResult = std::move(reportResult)](asio::yield_context yc) { - Timeout::Ptr timeout = new Timeout(strand->context(), *strand, boost::posix_time::microseconds(int64_t(checkTimeout * 1e6)), + Timeout::Ptr timeout = new Timeout(*strand, boost::posix_time::microseconds(int64_t(checkTimeout * 1e6)), [&conn, &checkable] { Log(LogNotice, "IfwApiCheckTask") << "Timeout while checking " << checkable->GetReflectionType()->GetName() diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 913470bd4..8a18caf7e 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -534,7 +534,7 @@ void ApiListener::ListenerCoroutineProc(boost::asio::yield_context yc, const Sha auto strand (Shared::Make(io)); IoEngine::SpawnCoroutine(*strand, [this, strand, sslConn, remoteEndpoint](asio::yield_context yc) { - Timeout::Ptr timeout(new Timeout(strand->context(), *strand, boost::posix_time::microseconds(int64_t(GetConnectTimeout() * 1e6)), + Timeout::Ptr timeout (new Timeout(*strand, boost::posix_time::microseconds(int64_t(GetConnectTimeout() * 1e6)), [sslConn, remoteEndpoint] { Log(LogWarning, "ApiListener") << "Timeout while processing incoming connection from " << remoteEndpoint; @@ -585,7 +585,7 @@ void ApiListener::AddConnection(const Endpoint::Ptr& endpoint) lock.unlock(); - Timeout::Ptr timeout(new Timeout(strand->context(), *strand, boost::posix_time::microseconds(int64_t(GetConnectTimeout() * 1e6)), + Timeout::Ptr timeout (new Timeout(*strand, boost::posix_time::microseconds(int64_t(GetConnectTimeout() * 1e6)), [sslConn, endpoint, host, port] { Log(LogCritical, "ApiListener") << "Timeout while reconnecting to endpoint '" << endpoint->GetName() << "' via host '" << host @@ -684,7 +684,6 @@ void ApiListener::NewClientHandlerInternal( { Timeout::Ptr handshakeTimeout (new Timeout( - strand->context(), *strand, boost::posix_time::microseconds(intmax_t(Configuration::TlsHandshakeTimeout * 1000000)), [client] { diff --git a/test/base-io-engine.cpp b/test/base-io-engine.cpp index 2261bab91..74cb67319 100644 --- a/test/base-io-engine.cpp +++ b/test/base-io-engine.cpp @@ -19,7 +19,7 @@ BOOST_AUTO_TEST_CASE(timeout_run) boost::asio::spawn(strand, [&](boost::asio::yield_context yc) { boost::asio::deadline_timer timer (io); - Timeout::Ptr timeout = new Timeout(io, strand, boost::posix_time::millisec(300), [&called] { ++called; }); + Timeout::Ptr timeout = new Timeout(strand, boost::posix_time::millisec(300), [&called] { ++called; }); BOOST_CHECK_EQUAL(called, 0); timer.expires_from_now(boost::posix_time::millisec(200)); @@ -42,7 +42,7 @@ BOOST_AUTO_TEST_CASE(timeout_cancelled) boost::asio::spawn(strand, [&](boost::asio::yield_context yc) { boost::asio::deadline_timer timer (io); - Timeout::Ptr timeout = new Timeout(io, strand, boost::posix_time::millisec(300), [&called] { ++called; }); + Timeout::Ptr timeout = new Timeout(strand, boost::posix_time::millisec(300), [&called] { ++called; }); timer.expires_from_now(boost::posix_time::millisec(200)); timer.async_wait(yc); @@ -68,7 +68,7 @@ BOOST_AUTO_TEST_CASE(timeout_scope) boost::asio::deadline_timer timer (io); { - Timeout::Ptr timeout = new Timeout(io, strand, boost::posix_time::millisec(300), [&called] { ++called; }); + Timeout::Ptr timeout = new Timeout(strand, boost::posix_time::millisec(300), [&called] { ++called; }); timer.expires_from_now(boost::posix_time::millisec(200)); timer.async_wait(yc); @@ -92,7 +92,7 @@ BOOST_AUTO_TEST_CASE(timeout_due_cancelled) boost::asio::spawn(strand, [&](boost::asio::yield_context yc) { boost::asio::deadline_timer timer (io); - Timeout::Ptr timeout = new Timeout(io, strand, boost::posix_time::millisec(300), [&called] { ++called; }); + Timeout::Ptr timeout = new Timeout(strand, boost::posix_time::millisec(300), [&called] { ++called; }); // Give the timeout enough time to become due while blocking its strand to prevent it from actually running... Utility::Sleep(0.4); @@ -122,7 +122,7 @@ BOOST_AUTO_TEST_CASE(timeout_due_scope) boost::asio::deadline_timer timer (io); { - Timeout::Ptr timeout = new Timeout(io, strand, boost::posix_time::millisec(300), [&called] { ++called; }); + Timeout::Ptr timeout = new Timeout(strand, boost::posix_time::millisec(300), [&called] { ++called; }); // Give the timeout enough time to become due while blocking its strand to prevent it from actually running... Utility::Sleep(0.4); From 959b162913e38699c4376fa839a857fae82e6c95 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 29 Nov 2024 14:16:08 +0100 Subject: [PATCH 131/415] Timeout#~Timeout(), #Cancel(): support boost::asio::io_context running on multiple threads --- lib/base/io-engine.cpp | 2 ++ lib/base/io-engine.hpp | 20 +++++++++++++++----- test/base-io-engine.cpp | 16 ++++++++++++++++ 3 files changed, 33 insertions(+), 5 deletions(-) diff --git a/lib/base/io-engine.cpp b/lib/base/io-engine.cpp index 2727236ad..246a448f2 100644 --- a/lib/base/io-engine.cpp +++ b/lib/base/io-engine.cpp @@ -148,6 +148,8 @@ void AsioConditionVariable::Wait(boost::asio::yield_context yc) void Timeout::Cancel() { + m_Cancelled->store(true); + boost::system::error_code ec; m_Timer.cancel(ec); } diff --git a/lib/base/io-engine.hpp b/lib/base/io-engine.hpp index a030d2044..919f773bc 100644 --- a/lib/base/io-engine.hpp +++ b/lib/base/io-engine.hpp @@ -3,10 +3,12 @@ #ifndef IO_ENGINE_H #define IO_ENGINE_H +#include "base/atomic.hpp" #include "base/debug.hpp" #include "base/exception.hpp" #include "base/lazy-init.hpp" #include "base/logger.hpp" +#include "base/shared.hpp" #include "base/shared-object.hpp" #include #include @@ -174,21 +176,29 @@ public: template Timeout(boost::asio::io_context::strand& strand, const Timer::duration_type& timeoutFromNow, OnTimeout onTimeout) - : m_Timer(strand.context(), timeoutFromNow) + : m_Timer(strand.context(), timeoutFromNow), m_Cancelled(Shared>::Make(false)) { VERIFY(strand.running_in_this_thread()); - m_Timer.async_wait(boost::asio::bind_executor(strand, [onTimeout = std::move(onTimeout)](boost::system::error_code ec) { - if (!ec) { - onTimeout(); + m_Timer.async_wait(boost::asio::bind_executor( + strand, [cancelled = m_Cancelled, onTimeout = std::move(onTimeout)](boost::system::error_code ec) { + if (!ec && !cancelled->load()) { + onTimeout(); + } } - })); + )); + } + + ~Timeout() override + { + Cancel(); } void Cancel(); private: Timer m_Timer; + Shared>::Ptr m_Cancelled; }; } diff --git a/test/base-io-engine.cpp b/test/base-io-engine.cpp index 74cb67319..d75ce706d 100644 --- a/test/base-io-engine.cpp +++ b/test/base-io-engine.cpp @@ -5,6 +5,7 @@ #include #include #include +#include using namespace icinga; @@ -30,7 +31,10 @@ BOOST_AUTO_TEST_CASE(timeout_run) timer.async_wait(yc); }); + std::thread eventLoop ([&io] { io.run(); }); io.run(); + eventLoop.join(); + BOOST_CHECK_EQUAL(called, 1); } @@ -54,7 +58,10 @@ BOOST_AUTO_TEST_CASE(timeout_cancelled) timer.async_wait(yc); }); + std::thread eventLoop ([&io] { io.run(); }); io.run(); + eventLoop.join(); + BOOST_CHECK_EQUAL(called, 0); } @@ -80,7 +87,10 @@ BOOST_AUTO_TEST_CASE(timeout_scope) timer.async_wait(yc); }); + std::thread eventLoop ([&io] { io.run(); }); io.run(); + eventLoop.join(); + BOOST_CHECK_EQUAL(called, 0); } @@ -108,7 +118,10 @@ BOOST_AUTO_TEST_CASE(timeout_due_cancelled) timer.async_wait(yc); }); + std::thread eventLoop ([&io] { io.run(); }); io.run(); + eventLoop.join(); + BOOST_CHECK_EQUAL(called, 0); } @@ -136,7 +149,10 @@ BOOST_AUTO_TEST_CASE(timeout_due_scope) timer.async_wait(yc); }); + std::thread eventLoop ([&io] { io.run(); }); io.run(); + eventLoop.join(); + BOOST_CHECK_EQUAL(called, 0); } From d77d7506f1379ffade375eb1d66afb414d9c36a1 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 2 Dec 2024 16:06:58 +0100 Subject: [PATCH 132/415] Don't call Timeout#Cancel() where Timeout#~Timeout() is called --- lib/base/tlsstream.cpp | 3 --- lib/icingadb/redisconnection.cpp | 3 --- lib/methods/ifwapichecktask.cpp | 2 -- lib/remote/apilistener.cpp | 4 ---- 4 files changed, 12 deletions(-) diff --git a/lib/base/tlsstream.cpp b/lib/base/tlsstream.cpp index d1153f2d4..b7280be09 100644 --- a/lib/base/tlsstream.cpp +++ b/lib/base/tlsstream.cpp @@ -146,9 +146,6 @@ void AsioTlsStream::GracefulDisconnect(boost::asio::io_context::strand& strand, ForceDisconnect(); } )); - Defer cancelTimeout ([&shutdownTimeout]() { - shutdownTimeout->Cancel(); - }); // Close the TLS connection, effectively uses SSL_shutdown() to send a close_notify shutdown alert to the peer. boost::system::error_code ec; diff --git a/lib/icingadb/redisconnection.cpp b/lib/icingadb/redisconnection.cpp index c187d7f1e..a6b82187d 100644 --- a/lib/icingadb/redisconnection.cpp +++ b/lib/icingadb/redisconnection.cpp @@ -318,7 +318,6 @@ void RedisConnection::Connect(asio::yield_context& yc) auto conn (Shared::Make(m_Strand.context(), *m_TLSContext, m_Host)); auto& tlsConn (conn->next_layer()); auto connectTimeout (MakeTimeout(conn)); - Defer cancelTimeout ([&connectTimeout]() { connectTimeout->Cancel(); }); icinga::Connect(conn->lowest_layer(), m_Host, Convert::ToString(m_Port), yc); tlsConn.async_handshake(tlsConn.client, yc); @@ -348,7 +347,6 @@ void RedisConnection::Connect(asio::yield_context& yc) auto conn (Shared::Make(m_Strand.context())); auto connectTimeout (MakeTimeout(conn)); - Defer cancelTimeout ([&connectTimeout]() { connectTimeout->Cancel(); }); icinga::Connect(conn->next_layer(), m_Host, Convert::ToString(m_Port), yc); Handshake(conn, yc); @@ -361,7 +359,6 @@ void RedisConnection::Connect(asio::yield_context& yc) auto conn (Shared::Make(m_Strand.context())); auto connectTimeout (MakeTimeout(conn)); - Defer cancelTimeout ([&connectTimeout]() { connectTimeout->Cancel(); }); conn->next_layer().async_connect(Unix::endpoint(m_Path.CStr()), yc); Handshake(conn, yc); diff --git a/lib/methods/ifwapichecktask.cpp b/lib/methods/ifwapichecktask.cpp index ad19507e7..43221d154 100644 --- a/lib/methods/ifwapichecktask.cpp +++ b/lib/methods/ifwapichecktask.cpp @@ -467,8 +467,6 @@ void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes } ); - Defer cancelTimeout ([&timeout]() { timeout->Cancel(); }); - DoIfwNetIo(yc, cr, psCommand, psHost, expectedSan, psPort, *conn, *req); cr->SetExecutionEnd(Utility::GetTime()); diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 8a18caf7e..a17fbbad6 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -543,7 +543,6 @@ void ApiListener::ListenerCoroutineProc(boost::asio::yield_context yc, const Sha sslConn->lowest_layer().cancel(ec); } )); - Defer cancelTimeout([timeout]() { timeout->Cancel(); }); NewClientHandler(yc, strand, sslConn, String(), RoleServer); }); @@ -595,7 +594,6 @@ void ApiListener::AddConnection(const Endpoint::Ptr& endpoint) sslConn->lowest_layer().cancel(ec); } )); - Defer cancelTimeout([&timeout]() { timeout->Cancel(); }); Connect(sslConn->lowest_layer(), host, port, yc); @@ -693,8 +691,6 @@ void ApiListener::NewClientHandlerInternal( )); sslConn.async_handshake(role == RoleClient ? sslConn.client : sslConn.server, yc[ec]); - - handshakeTimeout->Cancel(); } if (ec) { From 27e0e236cbabe0d8619ca921a5a82a5cb548e02b Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 2 Dec 2024 16:18:13 +0100 Subject: [PATCH 133/415] Move Timeout instances from heap to stack --- lib/base/io-engine.hpp | 6 ++---- lib/base/tlsstream.cpp | 4 ++-- lib/icingadb/redisconnection.hpp | 6 +++--- lib/methods/ifwapichecktask.cpp | 2 +- lib/remote/apilistener.cpp | 12 ++++++------ test/base-io-engine.cpp | 14 +++++++------- 6 files changed, 21 insertions(+), 23 deletions(-) diff --git a/lib/base/io-engine.hpp b/lib/base/io-engine.hpp index 919f773bc..466927d10 100644 --- a/lib/base/io-engine.hpp +++ b/lib/base/io-engine.hpp @@ -9,7 +9,6 @@ #include "base/lazy-init.hpp" #include "base/logger.hpp" #include "base/shared.hpp" -#include "base/shared-object.hpp" #include #include #include @@ -168,10 +167,9 @@ private: * * @ingroup base */ -class Timeout : public SharedObject +class Timeout { public: - DECLARE_PTR_TYPEDEFS(Timeout); using Timer = boost::asio::deadline_timer; template @@ -189,7 +187,7 @@ public: )); } - ~Timeout() override + ~Timeout() { Cancel(); } diff --git a/lib/base/tlsstream.cpp b/lib/base/tlsstream.cpp index b7280be09..66514e0cf 100644 --- a/lib/base/tlsstream.cpp +++ b/lib/base/tlsstream.cpp @@ -140,12 +140,12 @@ void AsioTlsStream::GracefulDisconnect(boost::asio::io_context::strand& strand, } { - Timeout::Ptr shutdownTimeout(new Timeout(strand, boost::posix_time::seconds(10), + Timeout shutdownTimeout (strand, boost::posix_time::seconds(10), [this] { // Forcefully terminate the connection if async_shutdown() blocked more than 10 seconds. ForceDisconnect(); } - )); + ); // Close the TLS connection, effectively uses SSL_shutdown() to send a close_notify shutdown alert to the peer. boost::system::error_code ec; diff --git a/lib/icingadb/redisconnection.hpp b/lib/icingadb/redisconnection.hpp index f73c1fbdf..3f963f3d3 100644 --- a/lib/icingadb/redisconnection.hpp +++ b/lib/icingadb/redisconnection.hpp @@ -222,7 +222,7 @@ namespace icinga void Handshake(StreamPtr& stream, boost::asio::yield_context& yc); template - Timeout::Ptr MakeTimeout(StreamPtr& stream); + Timeout MakeTimeout(StreamPtr& stream); String m_Path; String m_Host; @@ -512,9 +512,9 @@ void RedisConnection::Handshake(StreamPtr& strm, boost::asio::yield_context& yc) * @param stream Redis server connection */ template -Timeout::Ptr RedisConnection::MakeTimeout(StreamPtr& stream) +Timeout RedisConnection::MakeTimeout(StreamPtr& stream) { - return new Timeout( + return Timeout( m_Strand, boost::posix_time::microseconds(intmax_t(m_ConnectTimeout * 1000000)), [stream] { diff --git a/lib/methods/ifwapichecktask.cpp b/lib/methods/ifwapichecktask.cpp index 43221d154..ce48deefc 100644 --- a/lib/methods/ifwapichecktask.cpp +++ b/lib/methods/ifwapichecktask.cpp @@ -456,7 +456,7 @@ void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes IoEngine::SpawnCoroutine( *strand, [strand, checkable, cr, psCommand, psHost, expectedSan, psPort, conn, req, checkTimeout, reportResult = std::move(reportResult)](asio::yield_context yc) { - Timeout::Ptr timeout = new Timeout(*strand, boost::posix_time::microseconds(int64_t(checkTimeout * 1e6)), + Timeout timeout (*strand, boost::posix_time::microseconds(int64_t(checkTimeout * 1e6)), [&conn, &checkable] { Log(LogNotice, "IfwApiCheckTask") << "Timeout while checking " << checkable->GetReflectionType()->GetName() diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index a17fbbad6..519469aaf 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -534,7 +534,7 @@ void ApiListener::ListenerCoroutineProc(boost::asio::yield_context yc, const Sha auto strand (Shared::Make(io)); IoEngine::SpawnCoroutine(*strand, [this, strand, sslConn, remoteEndpoint](asio::yield_context yc) { - Timeout::Ptr timeout (new Timeout(*strand, boost::posix_time::microseconds(int64_t(GetConnectTimeout() * 1e6)), + Timeout timeout (*strand, boost::posix_time::microseconds(int64_t(GetConnectTimeout() * 1e6)), [sslConn, remoteEndpoint] { Log(LogWarning, "ApiListener") << "Timeout while processing incoming connection from " << remoteEndpoint; @@ -542,7 +542,7 @@ void ApiListener::ListenerCoroutineProc(boost::asio::yield_context yc, const Sha boost::system::error_code ec; sslConn->lowest_layer().cancel(ec); } - )); + ); NewClientHandler(yc, strand, sslConn, String(), RoleServer); }); @@ -584,7 +584,7 @@ void ApiListener::AddConnection(const Endpoint::Ptr& endpoint) lock.unlock(); - Timeout::Ptr timeout (new Timeout(*strand, boost::posix_time::microseconds(int64_t(GetConnectTimeout() * 1e6)), + Timeout timeout (*strand, boost::posix_time::microseconds(int64_t(GetConnectTimeout() * 1e6)), [sslConn, endpoint, host, port] { Log(LogCritical, "ApiListener") << "Timeout while reconnecting to endpoint '" << endpoint->GetName() << "' via host '" << host @@ -593,7 +593,7 @@ void ApiListener::AddConnection(const Endpoint::Ptr& endpoint) boost::system::error_code ec; sslConn->lowest_layer().cancel(ec); } - )); + ); Connect(sslConn->lowest_layer(), host, port, yc); @@ -681,14 +681,14 @@ void ApiListener::NewClientHandlerInternal( boost::system::error_code ec; { - Timeout::Ptr handshakeTimeout (new Timeout( + Timeout handshakeTimeout ( *strand, boost::posix_time::microseconds(intmax_t(Configuration::TlsHandshakeTimeout * 1000000)), [client] { boost::system::error_code ec; client->lowest_layer().cancel(ec); } - )); + ); sslConn.async_handshake(role == RoleClient ? sslConn.client : sslConn.server, yc[ec]); } diff --git a/test/base-io-engine.cpp b/test/base-io-engine.cpp index d75ce706d..869688b1a 100644 --- a/test/base-io-engine.cpp +++ b/test/base-io-engine.cpp @@ -20,7 +20,7 @@ BOOST_AUTO_TEST_CASE(timeout_run) boost::asio::spawn(strand, [&](boost::asio::yield_context yc) { boost::asio::deadline_timer timer (io); - Timeout::Ptr timeout = new Timeout(strand, boost::posix_time::millisec(300), [&called] { ++called; }); + Timeout timeout (strand, boost::posix_time::millisec(300), [&called] { ++called; }); BOOST_CHECK_EQUAL(called, 0); timer.expires_from_now(boost::posix_time::millisec(200)); @@ -46,12 +46,12 @@ BOOST_AUTO_TEST_CASE(timeout_cancelled) boost::asio::spawn(strand, [&](boost::asio::yield_context yc) { boost::asio::deadline_timer timer (io); - Timeout::Ptr timeout = new Timeout(strand, boost::posix_time::millisec(300), [&called] { ++called; }); + Timeout timeout (strand, boost::posix_time::millisec(300), [&called] { ++called; }); timer.expires_from_now(boost::posix_time::millisec(200)); timer.async_wait(yc); - timeout->Cancel(); + timeout.Cancel(); BOOST_CHECK_EQUAL(called, 0); timer.expires_from_now(boost::posix_time::millisec(200)); @@ -75,7 +75,7 @@ BOOST_AUTO_TEST_CASE(timeout_scope) boost::asio::deadline_timer timer (io); { - Timeout::Ptr timeout = new Timeout(strand, boost::posix_time::millisec(300), [&called] { ++called; }); + Timeout timeout (strand, boost::posix_time::millisec(300), [&called] { ++called; }); timer.expires_from_now(boost::posix_time::millisec(200)); timer.async_wait(yc); @@ -102,7 +102,7 @@ BOOST_AUTO_TEST_CASE(timeout_due_cancelled) boost::asio::spawn(strand, [&](boost::asio::yield_context yc) { boost::asio::deadline_timer timer (io); - Timeout::Ptr timeout = new Timeout(strand, boost::posix_time::millisec(300), [&called] { ++called; }); + Timeout timeout (strand, boost::posix_time::millisec(300), [&called] { ++called; }); // Give the timeout enough time to become due while blocking its strand to prevent it from actually running... Utility::Sleep(0.4); @@ -110,7 +110,7 @@ BOOST_AUTO_TEST_CASE(timeout_due_cancelled) BOOST_CHECK_EQUAL(called, 0); // ... so that this shall still work: - timeout->Cancel(); + timeout.Cancel(); BOOST_CHECK_EQUAL(called, 0); @@ -135,7 +135,7 @@ BOOST_AUTO_TEST_CASE(timeout_due_scope) boost::asio::deadline_timer timer (io); { - Timeout::Ptr timeout = new Timeout(strand, boost::posix_time::millisec(300), [&called] { ++called; }); + Timeout timeout (strand, boost::posix_time::millisec(300), [&called] { ++called; }); // Give the timeout enough time to become due while blocking its strand to prevent it from actually running... Utility::Sleep(0.4); From 3ca7ff7bf4149b1e059d299ea33c6b97fd2c6f30 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 2 Dec 2024 17:00:02 +0100 Subject: [PATCH 134/415] Timeout: explicitly delete #Timeout(const Timeout&), #Timeout(Timeout&&), #operator=(const Timeout&), #operator=(Timeout&&) --- lib/base/io-engine.hpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/base/io-engine.hpp b/lib/base/io-engine.hpp index 466927d10..e7cd63096 100644 --- a/lib/base/io-engine.hpp +++ b/lib/base/io-engine.hpp @@ -187,6 +187,11 @@ public: )); } + Timeout(const Timeout&) = delete; + Timeout(Timeout&&) = delete; + Timeout& operator=(const Timeout&) = delete; + Timeout& operator=(Timeout&&) = delete; + ~Timeout() { Cancel(); From 8f7289122888634574f090308806db4ecf58e4e8 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 29 Nov 2024 12:18:13 +0100 Subject: [PATCH 135/415] Document Timeout --- lib/base/io-engine.cpp | 5 +++++ lib/base/io-engine.hpp | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/lib/base/io-engine.cpp b/lib/base/io-engine.cpp index 246a448f2..3190ed03d 100644 --- a/lib/base/io-engine.cpp +++ b/lib/base/io-engine.cpp @@ -146,6 +146,11 @@ void AsioConditionVariable::Wait(boost::asio::yield_context yc) m_Timer.async_wait(yc[ec]); } +/** + * Cancels any pending timeout callback. + * + * Must be called in the strand in which the callback was scheduled! + */ void Timeout::Cancel() { m_Cancelled->store(true); diff --git a/lib/base/io-engine.hpp b/lib/base/io-engine.hpp index e7cd63096..0350d45b8 100644 --- a/lib/base/io-engine.hpp +++ b/lib/base/io-engine.hpp @@ -165,6 +165,22 @@ private: /** * I/O timeout emulator * + * This class provides a workaround for Boost.ASIO's lack of built-in timeout support. + * While Boost.ASIO handles asynchronous operations, it does not natively support timeouts for these operations. + * This class uses a boost::asio::deadline_timer to emulate a timeout by scheduling a callback to be triggered + * after a specified duration, effectively adding timeout behavior where none exists. + * The callback is executed within the provided strand, ensuring thread-safety. + * + * The constructor returns immediately after scheduling the timeout callback. + * The callback itself is invoked asynchronously when the timeout occurs. + * This allows the caller to continue execution while the timeout is running in the background. + * + * The class provides a Cancel() method to unschedule any pending callback. If the callback has already been run, + * calling Cancel() has no effect. This method can be used to abort the timeout early if the monitored operation + * completes before the callback has been run. The Timeout destructor also automatically cancels any pending callback. + * A callback is considered pending even if the timeout has already expired, + * but the callback has not been executed yet due to a busy strand. + * * @ingroup base */ class Timeout @@ -172,6 +188,14 @@ class Timeout public: using Timer = boost::asio::deadline_timer; + /** + * Schedules onTimeout to be triggered after timeoutFromNow on strand. + * + * @param strand The strand in which the callback will be executed. + * The caller must also run in this strand, as well as Cancel() and the destructor! + * @param timeoutFromNow The duration after which the timeout callback will be triggered. + * @param onTimeout The callback to invoke when the timeout occurs. + */ template Timeout(boost::asio::io_context::strand& strand, const Timer::duration_type& timeoutFromNow, OnTimeout onTimeout) : m_Timer(strand.context(), timeoutFromNow), m_Cancelled(Shared>::Make(false)) @@ -192,6 +216,11 @@ public: Timeout& operator=(const Timeout&) = delete; Timeout& operator=(Timeout&&) = delete; + /** + * Cancels any pending timeout callback. + * + * Must be called in the strand in which the callback was scheduled! + */ ~Timeout() { Cancel(); @@ -201,6 +230,14 @@ public: private: Timer m_Timer; + + /** + * Indicates whether the Timeout has been cancelled. + * + * This must be Shared<> between the lambda in the constructor and Cancel() for the case + * the destructor calls Cancel() while the lambda is already queued in the strand. + * The whole Timeout instance can't be kept alive by the lambda because this would delay the destructor. + */ Shared>::Ptr m_Cancelled; }; From a662cb1a6bdc792166f4e846d729fa635541345c Mon Sep 17 00:00:00 2001 From: MarcusCaepio <7324088+MarcusCaepio@users.noreply.github.com> Date: Thu, 30 Nov 2023 06:37:06 +0100 Subject: [PATCH 136/415] Update ITL for check_ssh Adds parameters for check_ssh (-r and -P) based on nagios-plugins 2.3.3 / monitoring-plugins 2.3 Fixes #9922 --- doc/10-icinga-template-library.md | 16 +++++++++------- itl/command-plugins.conf | 8 ++++++++ 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md index f20832437..9f43e93a6 100644 --- a/doc/10-icinga-template-library.md +++ b/doc/10-icinga-template-library.md @@ -1480,13 +1480,15 @@ connects to an SSH server at a specified host and port. Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -----------------|-------------- -ssh_address | **Optional.** The host's address. Defaults to "$address$" if the host's `address` attribute is set, "$address6$" otherwise. -ssh_port | **Optional.** The port that should be checked. Defaults to 22. -ssh_timeout | **Optional.** Seconds before connection times out. Defaults to 10. -ssh_ipv4 | **Optional.** Use IPv4 connection. Defaults to false. -ssh_ipv6 | **Optional.** Use IPv6 connection. Defaults to false. +Name | Description +--------------------|-------------- +ssh_address | **Optional.** The host's address. Defaults to "$address$" if the host's `address` attribute is set, "$address6$" otherwise. +ssh_port | **Optional.** The port that should be checked. Defaults to 22. +ssh_timeout | **Optional.** Seconds before connection times out. Defaults to 10. +ssh_ipv4 | **Optional.** Use IPv4 connection. Defaults to false. +ssh_ipv6 | **Optional.** Use IPv6 connection. Defaults to false. +ssh_remote_version | **Optional.** Alert if string doesn't match expected server version (ex: OpenSSH_3.9p1). +ssh_remote_protocol | **Optional.** Alert if protocol doesn't match expected protocol version (ex: 2.0). ### ssl diff --git a/itl/command-plugins.conf b/itl/command-plugins.conf index 5fc9ab435..7563801f9 100644 --- a/itl/command-plugins.conf +++ b/itl/command-plugins.conf @@ -1550,6 +1550,14 @@ object CheckCommand "ssh" { set_if = "$ssh_ipv6$" description = "Use IPv6 connection" } + "-r" = { + value = "$ssh_remote_version$" + description = "Alert if string doesn't match expected server version (ex: OpenSSH_3.9p1)" + } + "-P" = { + value = "$ssh_remote_protocol$" + description = "Alert if protocol doesn't match expected protocol version (ex: 2.0)" + } } vars.ssh_address = "$check_address$" From 920ba0b2dbe8144286916839a5d1049e8ac23427 Mon Sep 17 00:00:00 2001 From: Peter Eckel <6815386+peteeckel@users.noreply.github.com> Date: Wed, 8 Jan 2025 11:47:37 +0100 Subject: [PATCH 137/415] Added the `--dane` option to the command definition ssl_cert (#10196) --- doc/10-icinga-template-library.md | 1 + itl/plugins-contrib.d/web.conf | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md index f20832437..57ca1badf 100644 --- a/doc/10-icinga-template-library.md +++ b/doc/10-icinga-template-library.md @@ -5944,6 +5944,7 @@ ssl_cert_ignore_ocsp_errors | **Optional.** Continue if the OCSP status cannot ssl_cert_ignore_ocsp_timeout | **Optional.** Ignore OCSP result when timeout occurs while checking. ssl_cert_ignore_sct | **Optional.** Do not check for signed certificate timestamps. ssl_cert_ignore_tls_renegotiation | **Optional.** Do not check for renegotiation. +ssl_cert_dane | **Optional.** Verify that valid DANE records exist ({211,301,302,311,312} or empty string). #### jmx4perl diff --git a/itl/plugins-contrib.d/web.conf b/itl/plugins-contrib.d/web.conf index 5194ffb17..62ae886c9 100644 --- a/itl/plugins-contrib.d/web.conf +++ b/itl/plugins-contrib.d/web.conf @@ -582,6 +582,11 @@ object CheckCommand "ssl_cert" { value = "$ssl_cert_maximum_validity$" description = "The maximum validity of the certificate in days (default: 397)" } + "--dane" = { + value = "$ssl_cert_dane$" + description = "verify that valid DANE records exist (since OpenSSL 1.1.0)" + repeat_key = false + } } From b088d981ff633804c2fd17446316ad187250c6f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Wed, 8 Jan 2025 11:50:11 +0100 Subject: [PATCH 138/415] 21-development.md: fix indentation The last two points need to be children of the second one, but currently GitHub weights them equally. --- doc/21-development.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/21-development.md b/doc/21-development.md index 13d3207d9..4b7595f1a 100644 --- a/doc/21-development.md +++ b/doc/21-development.md @@ -1736,10 +1736,10 @@ and don't care for the details, 1. ensure there are 35 GB free space on C: 2. run the following in an administrative Powershell: - 1. `Enable-WindowsOptionalFeature -FeatureName "NetFx3" -Online` - (reboot when asked!) - 2. `powershell -NoProfile -ExecutionPolicy Bypass -Command "Invoke-Expression (New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/Icinga/icinga2/master/doc/win-dev.ps1')"` - (will take some time) + 1. `Enable-WindowsOptionalFeature -FeatureName "NetFx3" -Online` + (reboot when asked!) + 2. `powershell -NoProfile -ExecutionPolicy Bypass -Command "Invoke-Expression (New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/Icinga/icinga2/master/doc/win-dev.ps1')"` + (will take some time) This installs everything needed for cloning and building Icinga 2 on the command line (Powershell) as follows: From 339ee7b1253133b1bf9bfaf65171fafe306ca8e9 Mon Sep 17 00:00:00 2001 From: Blerim Sheqa Date: Wed, 8 Jan 2025 10:06:34 +0100 Subject: [PATCH 139/415] Fix and cleanup broken and obsolete links --- doc/02-installation.md | 10 +++++----- doc/04-configuration.md | 2 +- doc/08-advanced-topics.md | 2 +- doc/12-icinga2-api.md | 4 ++-- doc/13-addons.md | 14 +++----------- doc/15-troubleshooting.md | 9 ++++----- doc/21-development.md | 4 ++-- 7 files changed, 18 insertions(+), 27 deletions(-) diff --git a/doc/02-installation.md b/doc/02-installation.md index 35a7aef14..51d24076a 100644 --- a/doc/02-installation.md +++ b/doc/02-installation.md @@ -553,19 +553,19 @@ the Icinga DB daemon that synchronizes monitoring data between the Redis server The Icinga DB daemon package is also included in the Icinga repository, and since it is already set up, you have completed the instructions here and can proceed to -[install the Icinga DB daemon on Amazon Linux](https://icinga.com/docs/icinga-db/latest/doc/02-Installation/01-Amazon-Linux/#installing-icinga-db-package), +[install the Icinga DB daemon on Amazon Linux](https://icinga.com/docs/icinga-db/latest/doc/02-Installation/Amazon-Linux/#installing-the-package), -[install the Icinga DB daemon on Debian](https://icinga.com/docs/icinga-db/latest/doc/02-Installation/03-Debian/#installing-icinga-db-package), +[install the Icinga DB daemon on Debian](https://icinga.com/docs/icinga-db/latest/doc/02-Installation/Debian/#installing-the-package), -[install the Icinga DB daemon on RHEL](https://icinga.com/docs/icinga-db/latest/doc/02-Installation/04-RHEL/#installing-icinga-db-package), +[install the Icinga DB daemon on RHEL](https://icinga.com/docs/icinga-db/latest/doc/02-Installation/RHEL/#installing-the-package), -[install the Icinga DB daemon on SLES](https://icinga.com/docs/icinga-db/latest/doc/02-Installation/05-SLES/#installing-icinga-db-package), +[install the Icinga DB daemon on SLES](https://icinga.com/docs/icinga-db/latest/doc/02-Installation/SLES/#installing-the-package), -[install the Icinga DB daemon on Ubuntu](https://icinga.com/docs/icinga-db/latest/doc/02-Installation/06-Ubuntu/#installing-icinga-db-package), +[install the Icinga DB daemon on Ubuntu](https://icinga.com/docs/icinga-db/latest/doc/02-Installation/Ubuntu/#installing-the-package), which will also guide you through the setup of the database and Icinga DB Web. diff --git a/doc/04-configuration.md b/doc/04-configuration.md index e16c21060..2364ef73e 100644 --- a/doc/04-configuration.md +++ b/doc/04-configuration.md @@ -593,7 +593,7 @@ Read more on that topic [here](03-monitoring-basics.md#notification-commands). #### groups.conf -The example host defined in [hosts.conf](hosts-conf) already has the +The example host defined in [hosts.conf](#hosts-conf) already has the custom variable `os` set to `Linux` and is therefore automatically a member of the host group `linux-servers`. diff --git a/doc/08-advanced-topics.md b/doc/08-advanced-topics.md index 34330ed9e..4f468da61 100644 --- a/doc/08-advanced-topics.md +++ b/doc/08-advanced-topics.md @@ -484,7 +484,7 @@ host or service is considered flapping until it drops below the low flapping thr The attribute `flapping_ignore_states` allows to ignore state changes to specified states during the flapping calculation. `FlappingStart` and `FlappingEnd` notifications will be sent out accordingly, if configured. See the chapter on -[notifications](alert-notifications) for details +[notifications](03-monitoring-basics.md#notifications) for details > Note: There is no distinctions between hard and soft states with flapping. All state changes count and notifications > will be sent out regardless of the objects state. diff --git a/doc/12-icinga2-api.md b/doc/12-icinga2-api.md index c682befa8..6cb948132 100644 --- a/doc/12-icinga2-api.md +++ b/doc/12-icinga2-api.md @@ -1009,7 +1009,7 @@ curl -k -s -S -i -u root:icinga -H 'Accept: application/json' \ There are several actions available for Icinga 2 provided by the `/v1/actions` URL endpoint. You can run actions by sending a `POST` request. -The following actions are also used by [Icinga Web 2](https://icinga.com/products/icinga-web-2/): +The following actions are also used by [Icinga Web 2](https://icinga.com/docs/icinga-web/latest/): * sending check results to Icinga from scripts, remote agents, etc. * scheduling downtimes from external scripts or cronjobs @@ -2699,7 +2699,7 @@ The following languages are covered: * [Golang](12-icinga2-api.md#icinga2-api-clients-programmatic-examples-golang) * [Powershell](12-icinga2-api.md#icinga2-api-clients-programmatic-examples-powershell) -The [request method](icinga2-api-requests) is `POST` using [X-HTTP-Method-Override: GET](12-icinga2-api.md#icinga2-api-requests-method-override) +The [request method](#icinga2-api-requests) is `POST` using [X-HTTP-Method-Override: GET](12-icinga2-api.md#icinga2-api-requests-method-override) which allows you to send a JSON request body. The examples request specific service attributes joined with host attributes. `attrs` and `joins` are therefore specified as array. diff --git a/doc/13-addons.md b/doc/13-addons.md index f92982a94..d9c1c66cd 100644 --- a/doc/13-addons.md +++ b/doc/13-addons.md @@ -71,9 +71,6 @@ via email. ![Icinga Reporting](images/addons/icinga_reporting.png) -Follow along in this [hands-on blog post](https://icinga.com/2019/06/17/icinga-reporting-hands-on/). - - ## Graphs and Metrics ### Graphite @@ -185,7 +182,7 @@ in a tree or list overview and can be added to any dashboard. ![Icinga Web 2 Business Process](images/addons/icingaweb2_businessprocess.png) -Read more [here](https://icinga.com/products/icinga-business-process-modelling/). +Read more [here](https://icinga.com/docs/icinga-business-process-modeling/latest/). ### Certificate Monitoring @@ -194,8 +191,7 @@ actions and view all details at a glance. ![Icinga Certificate Monitoring](images/addons/icinga_certificate_monitoring.png) -Read more [here](https://icinga.com/products/icinga-certificate-monitoring/) -and [here](https://icinga.com/2019/06/03/monitoring-automation-with-icinga-certificate-monitoring/). +Read more [here](https://icinga.com/products/icinga-certificate-monitoring/). ### Dashing Dashboard @@ -204,7 +200,7 @@ on top of Dashing and uses the [REST API](12-icinga2-api.md#icinga2-api) to visu on with your monitoring. It combines several popular widgets and provides development instructions for your own implementation. -The dashboard also allows to embed the [Icinga Web 2](https://icinga.com/products/icinga-web-2/) +The dashboard also allows to embed the [Icinga Web 2](https://icinga.com/docs/icinga-web/latest/) host and service problem lists as Iframe. ![Dashing dashboard](images/addons/dashing_icinga2.png) @@ -234,10 +230,6 @@ There's a variety of resources available, for example different notification scr * Ticket systems * etc. -Blog posts and howtos: - -* [Environmental Monitoring and Alerting](https://icinga.com/2019/09/02/environmental-monitoring-and-alerting-via-text-message/) - Additionally external services can be [integrated with Icinga 2](https://icinga.com/products/integrations/): * [Pagerduty](https://icinga.com/products/integrations/pagerduty/) diff --git a/doc/15-troubleshooting.md b/doc/15-troubleshooting.md index 5afbebea9..67f2de65a 100644 --- a/doc/15-troubleshooting.md +++ b/doc/15-troubleshooting.md @@ -19,8 +19,8 @@ findings and details please. * `icinga2 --version` * `icinga2 feature list` * `icinga2 daemon -C` - * [Icinga Web 2](https://icinga.com/products/icinga-web-2/) version (screenshot from System - About) - * [Icinga Web 2 modules](https://icinga.com/products/icinga-web-2-modules/) e.g. the Icinga Director (optional) + * [Icinga Web 2](https://icinga.com/docs/icinga-web/latest/) version (screenshot from System - About) + * Icinga Web 2 modules e.g. the Icinga Director (optional) * Configuration insights: * Provide complete configuration snippets explaining your problem in detail * Your [icinga2.conf](04-configuration.md#icinga2-conf) file @@ -872,7 +872,7 @@ trying because you probably have a problem that requires manual intervention. ### Late Check Results -[Icinga Web 2](https://icinga.com/products/icinga-web-2/) provides +[Icinga Web 2](https://icinga.com/docs/icinga-web/latest/) provides a dashboard overview for `overdue checks`. The REST API provides the [status](12-icinga2-api.md#icinga2-api-status) URL endpoint with some generic metrics @@ -887,8 +887,7 @@ You can also calculate late check results via the REST API: * Fetch the `last_check` timestamp from each object * Compare the timestamp with the current time and add `check_interval` multiple times (change it to see which results are really late, like five times check_interval) -You can use the [icinga2 console](11-cli-commands.md#cli-command-console) to connect to the instance, fetch all data -and calculate the differences. More infos can be found in [this blogpost](https://icinga.com/2016/08/11/analyse-icinga-2-problems-using-the-console-api/). +You can use the [icinga2 console](11-cli-commands.md#cli-command-console) to connect to the instance, fetch all data and calculate the differences. ``` # ICINGA2_API_USERNAME=root ICINGA2_API_PASSWORD=icinga icinga2 console --connect 'https://localhost:5665/' diff --git a/doc/21-development.md b/doc/21-development.md index 13d3207d9..72b2e859d 100644 --- a/doc/21-development.md +++ b/doc/21-development.md @@ -2334,7 +2334,7 @@ for implementation details. CMake determines the Icinga 2 version number using `git describe` if the source directory is contained in a Git repository. Otherwise the version number -is extracted from the [ICINGA2_VERSION](ICINGA2_VERSION) file. This behavior can be +is extracted from the `ICINGA2_VERSION` file. This behavior can be overridden by creating a file called `icinga-version.h.force` in the source directory. Alternatively the `-DICINGA2_GIT_VERSION_INFO=OFF` option for CMake can be used to disable the usage of `git describe`. @@ -2520,7 +2520,7 @@ chmod +x /etc/init.d/icinga2 Icinga 2 reads a single configuration file which is used to specify all configuration settings (global settings, hosts, services, etc.). The -configuration format is explained in detail in the [doc/](doc/) directory. +configuration format is explained in detail in the `doc/` directory. By default `make install` installs example configuration files in `/usr/local/etc/icinga2` unless you have specified a different prefix or From 3af7cfe2ec2c5c9b4dda9caac62af5d83df798ea Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 31 Oct 2024 10:19:40 +0100 Subject: [PATCH 140/415] JsonRpcConnection: Don't drop client from cache prematurely PR #7445 incorrectly assumed that a peer that had already disconnected and never reconnected was due to the endpoint client being dropped after a successful socket shutdown. However, the issue at that time was that there was not a single timeout guards that could cancel the `async_shutdown` call, petentially blocking indefinetely. Although removing the client from cache early might have allowed the endpoint to reconnect, it did not resolve the underlying problem. Now that we have a proper cancellation timeout, we can wait until the currently used socket is fully closed before dropping the client from our cache. When our socket termination works reliably, the `ApiListener` reconnect timer should attempt to reconnect this endpoint after the next tick. Additionally, we now have logs both for before and after socket termination, which may help identify if it is hanging somewhere in between. --- lib/remote/jsonrpcconnection.cpp | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/lib/remote/jsonrpcconnection.cpp b/lib/remote/jsonrpcconnection.cpp index d49c0b359..d4455e393 100644 --- a/lib/remote/jsonrpcconnection.cpp +++ b/lib/remote/jsonrpcconnection.cpp @@ -254,16 +254,6 @@ void JsonRpcConnection::Disconnect() Log(LogWarning, "JsonRpcConnection") << "API client disconnected for identity '" << m_Identity << "'"; - // We need to unregister the endpoint client as soon as possible not to confuse Icinga 2, - // given that Endpoint::GetConnected() is just performing a check that the endpoint's client - // cache is not empty, which could result in an already disconnected endpoint never trying to - // reconnect again. See #7444. - if (m_Endpoint) { - m_Endpoint->RemoveClient(this); - } else { - ApiListener::GetInstance()->RemoveAnonymousClient(this); - } - m_OutgoingMessagesQueued.Set(); m_WriterDone.Wait(yc); @@ -272,6 +262,12 @@ void JsonRpcConnection::Disconnect() m_HeartbeatTimer.cancel(); m_Stream->GracefulDisconnect(m_IoStrand, yc); + + if (m_Endpoint) { + m_Endpoint->RemoveClient(this); + } else { + ApiListener::GetInstance()->RemoveAnonymousClient(this); + } }); } } From 41373ad0e55838ed257e340c953d4432b8aee69c Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 25 Nov 2024 11:30:10 +0100 Subject: [PATCH 141/415] Log before & after an RPC client is disconnected --- lib/remote/jsonrpcconnection.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/remote/jsonrpcconnection.cpp b/lib/remote/jsonrpcconnection.cpp index d4455e393..47a0d2b79 100644 --- a/lib/remote/jsonrpcconnection.cpp +++ b/lib/remote/jsonrpcconnection.cpp @@ -250,10 +250,10 @@ void JsonRpcConnection::Disconnect() if (!m_ShuttingDown.exchange(true)) { JsonRpcConnection::Ptr keepAlive (this); - IoEngine::SpawnCoroutine(m_IoStrand, [this, keepAlive](asio::yield_context yc) { - Log(LogWarning, "JsonRpcConnection") - << "API client disconnected for identity '" << m_Identity << "'"; + Log(LogNotice, "JsonRpcConnection") + << "Disconnecting API client for identity '" << m_Identity << "'"; + IoEngine::SpawnCoroutine(m_IoStrand, [this, keepAlive](asio::yield_context yc) { m_OutgoingMessagesQueued.Set(); m_WriterDone.Wait(yc); @@ -268,6 +268,9 @@ void JsonRpcConnection::Disconnect() } else { ApiListener::GetInstance()->RemoveAnonymousClient(this); } + + Log(LogWarning, "JsonRpcConnection") + << "API client disconnected for identity '" << m_Identity << "'"; }); } } From 142564193144d2f8c64cf4b9e159eb5ddbcee378 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 28 Nov 2024 11:06:44 +0100 Subject: [PATCH 142/415] Don't endlessly wait on writer coroutine on disconnect --- lib/remote/jsonrpcconnection.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/remote/jsonrpcconnection.cpp b/lib/remote/jsonrpcconnection.cpp index 47a0d2b79..b8ae22aaa 100644 --- a/lib/remote/jsonrpcconnection.cpp +++ b/lib/remote/jsonrpcconnection.cpp @@ -256,7 +256,22 @@ void JsonRpcConnection::Disconnect() IoEngine::SpawnCoroutine(m_IoStrand, [this, keepAlive](asio::yield_context yc) { m_OutgoingMessagesQueued.Set(); - m_WriterDone.Wait(yc); + { + Timeout writerTimeout( + m_IoStrand, + boost::posix_time::seconds(5), + [this]() { + // The writer coroutine could not finish soon enough to unblock the waiter down blow, + // so we have to do this on our own, and the coroutine will be terminated forcibly when + // the ops on the underlying socket are cancelled. + boost::system::error_code ec; + m_Stream->lowest_layer().cancel(ec); + } + ); + + m_WriterDone.Wait(yc); + // We don't need to explicitly cancel the timer here; its destructor will handle it for us. + } m_CheckLivenessTimer.cancel(); m_HeartbeatTimer.cancel(); From e7381193c8f5939dd69c38d43fb2e93d71824ea1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lorenz=20K=C3=A4stle?= <12514511+RincewindsHat@users.noreply.github.com> Date: Sun, 9 Jun 2024 13:41:50 +0200 Subject: [PATCH 143/415] Reject infinite performance data values Some fault monitoring plugins may return "inf" or "-inf" as values due to a failure to initialize or other errors. This patch introduces a check on whether the parse value is infinite (or negative infinite) and rejects the data point if that is the case. The reasoning here is: There is no possible way a value of "inf" is ever a true measuring or even useful. Furthermore, when passed to the performance data writers, it may be rejected by the backend and lead to further complications. --- lib/base/perfdatavalue.cpp | 4 ++++ test/icinga-perfdata.cpp | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/lib/base/perfdatavalue.cpp b/lib/base/perfdatavalue.cpp index aca8c0f3a..217167e01 100644 --- a/lib/base/perfdatavalue.cpp +++ b/lib/base/perfdatavalue.cpp @@ -259,6 +259,10 @@ PerfdataValue::Ptr PerfdataValue::Parse(const String& perfdata) double value = Convert::ToDouble(tokens[0].SubStr(0, pos)); + if (!std::isfinite(value)) { + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid performance data value: " + perfdata + " is outside of any reasonable range")); + } + bool counter = false; String unit; Value warn, crit, min, max; diff --git a/test/icinga-perfdata.cpp b/test/icinga-perfdata.cpp index 1318c08af..68d0ea277 100644 --- a/test/icinga-perfdata.cpp +++ b/test/icinga-perfdata.cpp @@ -336,6 +336,10 @@ BOOST_AUTO_TEST_CASE(invalid) BOOST_CHECK_THROW(PerfdataValue::Parse("test=1;1;123,456;1;1"), boost::exception); BOOST_CHECK_THROW(PerfdataValue::Parse("test=1;1;1;123,456;1"), boost::exception); BOOST_CHECK_THROW(PerfdataValue::Parse("test=1;1;1;1;123,456"), boost::exception); + BOOST_CHECK_THROW(PerfdataValue::Parse("test=inf"), boost::exception); + BOOST_CHECK_THROW(PerfdataValue::Parse("test=Inf"), boost::exception); + BOOST_CHECK_THROW(PerfdataValue::Parse("test=-inf"), boost::exception); + BOOST_CHECK_THROW(PerfdataValue::Parse("test=-Inf"), boost::exception); } BOOST_AUTO_TEST_CASE(multi) From ab0f20d8d6354af140967b0d2773c97eadac811e Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 13 Jan 2025 11:29:38 +0100 Subject: [PATCH 144/415] 21-development.md: support Windows Server That OS need NetFx3ServerFeatures to be enabled before NetFx3. --- doc/21-development.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/21-development.md b/doc/21-development.md index 4b7595f1a..3cfec12d5 100644 --- a/doc/21-development.md +++ b/doc/21-development.md @@ -1736,9 +1736,11 @@ and don't care for the details, 1. ensure there are 35 GB free space on C: 2. run the following in an administrative Powershell: - 1. `Enable-WindowsOptionalFeature -FeatureName "NetFx3" -Online` + 1. Windows Server only: + `Enable-WindowsOptionalFeature -FeatureName NetFx3ServerFeatures -Online` + 2. `Enable-WindowsOptionalFeature -FeatureName NetFx3 -Online` (reboot when asked!) - 2. `powershell -NoProfile -ExecutionPolicy Bypass -Command "Invoke-Expression (New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/Icinga/icinga2/master/doc/win-dev.ps1')"` + 3. `powershell -NoProfile -ExecutionPolicy Bypass -Command "Invoke-Expression (New-Object Net.WebClient).DownloadString('https://raw.githubusercontent.com/Icinga/icinga2/master/doc/win-dev.ps1')"` (will take some time) This installs everything needed for cloning and building Icinga 2 From 6195a457a79ef7307cd0d9231dc88bb65cd81f33 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 24 Mar 2023 15:37:57 +0100 Subject: [PATCH 145/415] Silence compiler warnings in code we don't maintain --- CMakeLists.txt | 12 ++++++------ icinga-app/CMakeLists.txt | 2 +- lib/base/CMakeLists.txt | 8 ++++---- lib/db_ido_mysql/CMakeLists.txt | 2 +- lib/db_ido_pgsql/CMakeLists.txt | 2 +- lib/icingadb/CMakeLists.txt | 2 +- lib/mysql_shim/CMakeLists.txt | 2 +- lib/pgsql_shim/CMakeLists.txt | 2 +- third-party/cmake/BoostTestTargets.cmake | 2 +- 9 files changed, 17 insertions(+), 17 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6e531a059..117135113 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -186,21 +186,21 @@ add_definitions(-DBOOST_FILESYSTEM_NO_DEPRECATED) add_definitions(-DBOOST_ASIO_USE_TS_EXECUTOR_AS_DEFAULT) link_directories(${Boost_LIBRARY_DIRS}) -include_directories(${Boost_INCLUDE_DIRS}) +include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) find_package(OpenSSL REQUIRED) -include_directories(${OPENSSL_INCLUDE_DIR}) +include_directories(SYSTEM ${OPENSSL_INCLUDE_DIR}) set(base_DEPS ${CMAKE_DL_LIBS} ${Boost_LIBRARIES} ${OPENSSL_LIBRARIES}) set(base_OBJS $ $ $) # JSON find_package(JSON) -include_directories(${JSON_INCLUDE}) +include_directories(SYSTEM ${JSON_INCLUDE}) # UTF8CPP find_package(UTF8CPP) -include_directories(${UTF8CPP_INCLUDE}) +include_directories(SYSTEM ${UTF8CPP_INCLUDE}) find_package(Editline) set(HAVE_EDITLINE "${EDITLINE_FOUND}") @@ -223,12 +223,12 @@ endif() if(EDITLINE_FOUND) list(APPEND base_DEPS ${EDITLINE_LIBRARIES}) - include_directories(${EDITLINE_INCLUDE_DIR}) + include_directories(SYSTEM ${EDITLINE_INCLUDE_DIR}) endif() if(TERMCAP_FOUND) list(APPEND base_DEPS ${TERMCAP_LIBRARIES}) - include_directories(${TERMCAP_INCLUDE_DIR}) + include_directories(SYSTEM ${TERMCAP_INCLUDE_DIR}) endif() if(WIN32) diff --git a/icinga-app/CMakeLists.txt b/icinga-app/CMakeLists.txt index ef71ad999..d5e120100 100644 --- a/icinga-app/CMakeLists.txt +++ b/icinga-app/CMakeLists.txt @@ -19,7 +19,7 @@ set_target_properties ( FOLDER Lib ) -include_directories(${Boost_INCLUDE_DIRS}) +include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) if(ICINGA2_WITH_CHECKER) list(APPEND icinga_app_SOURCES $) diff --git a/lib/base/CMakeLists.txt b/lib/base/CMakeLists.txt index e50e330e4..a60d7eb3c 100644 --- a/lib/base/CMakeLists.txt +++ b/lib/base/CMakeLists.txt @@ -130,7 +130,7 @@ if(HAVE_SYSTEMD) find_path(SYSTEMD_INCLUDE_DIR NAMES systemd/sd-daemon.h HINTS ${SYSTEMD_ROOT_DIR}) - include_directories(${SYSTEMD_INCLUDE_DIR}) + include_directories(SYSTEM ${SYSTEMD_INCLUDE_DIR}) set_property( SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/journaldlogger.cpp APPEND PROPERTY COMPILE_DEFINITIONS @@ -140,13 +140,13 @@ endif() add_library(base OBJECT ${base_SOURCES}) -include_directories(${icinga2_SOURCE_DIR}/third-party/execvpe) +include_directories(SYSTEM ${icinga2_SOURCE_DIR}/third-party/execvpe) link_directories(${icinga2_BINARY_DIR}/third-party/execvpe) -include_directories(${icinga2_SOURCE_DIR}/third-party/mmatch) +include_directories(SYSTEM ${icinga2_SOURCE_DIR}/third-party/mmatch) link_directories(${icinga2_BINARY_DIR}/third-party/mmatch) -include_directories(${icinga2_SOURCE_DIR}/third-party/socketpair) +include_directories(SYSTEM ${icinga2_SOURCE_DIR}/third-party/socketpair) link_directories(${icinga2_BINARY_DIR}/third-party/socketpair) set_target_properties ( diff --git a/lib/db_ido_mysql/CMakeLists.txt b/lib/db_ido_mysql/CMakeLists.txt index 70cb90db1..d99edc537 100644 --- a/lib/db_ido_mysql/CMakeLists.txt +++ b/lib/db_ido_mysql/CMakeLists.txt @@ -12,7 +12,7 @@ endif() add_library(db_ido_mysql OBJECT ${db_ido_mysql_SOURCES}) -include_directories(${MYSQL_INCLUDE_DIR}) +include_directories(SYSTEM ${MYSQL_INCLUDE_DIR}) add_dependencies(db_ido_mysql base config icinga db_ido) diff --git a/lib/db_ido_pgsql/CMakeLists.txt b/lib/db_ido_pgsql/CMakeLists.txt index e081a6278..191a76a9b 100644 --- a/lib/db_ido_pgsql/CMakeLists.txt +++ b/lib/db_ido_pgsql/CMakeLists.txt @@ -12,7 +12,7 @@ endif() add_library(db_ido_pgsql OBJECT ${db_ido_pgsql_SOURCES}) -include_directories(${PostgreSQL_INCLUDE_DIRS}) +include_directories(SYSTEM ${PostgreSQL_INCLUDE_DIRS}) add_dependencies(db_ido_pgsql base config icinga db_ido) diff --git a/lib/icingadb/CMakeLists.txt b/lib/icingadb/CMakeLists.txt index de8e4adae..133fb7d6d 100644 --- a/lib/icingadb/CMakeLists.txt +++ b/lib/icingadb/CMakeLists.txt @@ -15,7 +15,7 @@ endif() add_library(icingadb OBJECT ${icingadb_SOURCES}) -include_directories(${icinga2_SOURCE_DIR}/third-party) +include_directories(SYSTEM ${icinga2_SOURCE_DIR}/third-party) add_dependencies(icingadb base config icinga remote) diff --git a/lib/mysql_shim/CMakeLists.txt b/lib/mysql_shim/CMakeLists.txt index fc7dbeec3..8b982393a 100644 --- a/lib/mysql_shim/CMakeLists.txt +++ b/lib/mysql_shim/CMakeLists.txt @@ -1,6 +1,6 @@ # Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ -include_directories(${MYSQL_INCLUDE_DIR}) +include_directories(SYSTEM ${MYSQL_INCLUDE_DIR}) set(mysql_shim_SOURCES mysql_shim.def diff --git a/lib/pgsql_shim/CMakeLists.txt b/lib/pgsql_shim/CMakeLists.txt index 327b64a9d..06395ac99 100644 --- a/lib/pgsql_shim/CMakeLists.txt +++ b/lib/pgsql_shim/CMakeLists.txt @@ -1,7 +1,7 @@ # Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ link_directories(${PostgreSQL_LIBRARY_DIRS}) -include_directories(${PostgreSQL_INCLUDE_DIRS}) +include_directories(SYSTEM ${PostgreSQL_INCLUDE_DIRS}) set(pgsql_shim_SOURCES pgsql_shim.def diff --git a/third-party/cmake/BoostTestTargets.cmake b/third-party/cmake/BoostTestTargets.cmake index 4555dff72..13d6e8088 100644 --- a/third-party/cmake/BoostTestTargets.cmake +++ b/third-party/cmake/BoostTestTargets.cmake @@ -130,7 +130,7 @@ function(add_boost_test _name) if(Boost_FOUND) - include_directories(${Boost_INCLUDE_DIRS}) + include_directories(SYSTEM ${Boost_INCLUDE_DIRS}) set(includeType) foreach(src ${SOURCES}) From 1c5dfc58ea9b3786e40d6e989637057897f7aa26 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 11 Nov 2024 17:12:47 +0100 Subject: [PATCH 146/415] Always compile with -Wsuggest-override not to forget `override` `override` indicates an override of a virtual method and refuses to compile in case of a signature mismatch across the inheritence hierarchy. This is especially useful when signatures change not to forget anything. Hence, we shall always use `override` whereever applicable. --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 117135113..f6b297b5a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -239,6 +239,8 @@ set(CMAKE_MACOSX_RPATH 1) set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH};${CMAKE_INSTALL_FULL_LIBDIR}/icinga2") if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -Winconsistent-missing-override") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Qunused-arguments -fcolor-diagnostics -fno-limit-debug-info") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Qunused-arguments -fcolor-diagnostics -fno-limit-debug-info") @@ -256,6 +258,8 @@ if(CMAKE_C_COMPILER_ID STREQUAL "SunPro") endif() if(CMAKE_C_COMPILER_ID STREQUAL "GNU") + set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -Wsuggest-override") + if(CMAKE_SYSTEM_NAME MATCHES AIX) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -lpthread") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -lpthread") From daf36ae362437d84ebf433045e51f655cce50087 Mon Sep 17 00:00:00 2001 From: Bruno Lingner Date: Thu, 14 May 2020 14:17:31 +0200 Subject: [PATCH 147/415] add _extra_opts argument to plugins that support it --- doc/10-icinga-template-library.md | 62 ++++++++++-- itl/command-plugins.conf | 152 ++++++++++++++++++++++++++++++ 2 files changed, 205 insertions(+), 9 deletions(-) diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md index d0d9e5672..485e6b7e0 100644 --- a/doc/10-icinga-template-library.md +++ b/doc/10-icinga-template-library.md @@ -270,7 +270,6 @@ Custom variables passed as [command parameters](03-monitoring-basics.md#command- Name | Description ------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -apt_extra_opts | **Optional.** Read options from an ini file. apt_upgrade | **Optional.** [Default] Perform an upgrade. If an optional OPTS argument is provided, apt-get will be run with these command line options instead of the default. apt_dist_upgrade | **Optional.** Perform a dist-upgrade instead of normal upgrade. Like with -U OPTS can be provided to override the default options. apt_include | **Optional.** Include only packages matching REGEXP. Can be specified multiple times the values will be combined together. @@ -279,6 +278,7 @@ apt_critical | **Optional.** If the full package information of any o apt_timeout | **Optional.** Seconds before plugin times out (default: 10). apt_only_critical | **Optional.** Only warn about critical upgrades. apt_list | **Optional.** List packages available for upgrade. +apt_extra_opts | **Optional.** Read extra plugin options from an ini file. ### breeze @@ -319,6 +319,7 @@ by_ssh_options | **Optional.** Call ssh with '-o OPTION' (multiple options m by_ssh_ipv4 | **Optional.** Use IPv4 connection. Defaults to false. by_ssh_ipv6 | **Optional.** Use IPv6 connection. Defaults to false. by_ssh_skip_stderr | **Optional.** Ignore all or (if specified) first n lines on STDERR. +by_ssh_extra_opts | **Optional.** Read extra plugin options from an ini file. ### clamd @@ -350,6 +351,7 @@ clamd_ctime | **Optional.** Response time to result in critical status clamd_timeout | **Optional.** Seconds before connection times out. Defaults to 10. clamd_ipv4 | **Optional.** Use IPv4 connection. Defaults to false. clamd_ipv6 | **Optional.** Use IPv6 connection. Defaults to false. +clamd_extra_opts | **Optional.** Read extra plugin options from an ini file. ### dhcp @@ -367,6 +369,7 @@ dhcp_timeout | **Optional.** The timeout in seconds. dhcp_interface | **Optional.** The interface to use. dhcp_mac | **Optional.** The MAC address to use in the DHCP request. dhcp_unicast | **Optional.** Whether to use unicast requests. Defaults to false. +dhcp_extra_opts | **Optional.** Read extra plugin options from an ini file. ### dig @@ -390,6 +393,7 @@ dig_critical | **Optional.** Response time to result in critical status dig_timeout | **Optional.** Seconds before connection times out (default: 10). dig_ipv4 | **Optional.** Force dig to only use IPv4 query transport. Defaults to false. dig_ipv6 | **Optional.** Force dig to only use IPv6 query transport. Defaults to false. +dig_extra_opts | **Optional.** Read extra plugin options from an ini file. ### disk @@ -430,6 +434,7 @@ disk\_units | **Optional.** Choose bytes, kB, MB, GB, TB. disk\_exclude\_type | **Optional.** Ignore all filesystems of indicated type. Multiple regular expression strings must be defined as array. Defaults to "none", "tmpfs", "sysfs", "proc", "configfs", "devtmpfs", "devfs", "mtmfs", "tracefs", "cgroup", "fuse.\*" (only Monitoring Plugins support this so far), "fuse.gvfsd-fuse", "fuse.gvfs-fuse-daemon", "fuse.sshfs", "fdescfs", "overlay", "nsfs", "squashfs". disk\_include\_type | **Optional.** Check only filesystems of indicated type. Multiple regular expression strings must be defined as array. disk\_inode\_perfdata | **Optional.** Display inode usage in perfdata +disk\_extra\_opts | **Optional.** Read extra plugin options from an ini file. ### disk_smb @@ -471,6 +476,7 @@ dns_accept_cname | **Optional.** Accept cname responses as a valid result to dns_wtime | **Optional.** Return warning if elapsed time exceeds value. dns_ctime | **Optional.** Return critical if elapsed time exceeds value. dns_timeout | **Optional.** Seconds before connection times out. Defaults to 10. +dns_extra_opts | **Optional.** Read extra plugin options from an ini file. @@ -527,6 +533,7 @@ fping_bytes | **Optional.** The size of ICMP packet. fping_target_timeout | **Optional.** The target timeout in milli-seconds. fping_source_ip | **Optional.** The name or ip address of the source ip. fping_source_interface | **Optional.** The source interface name. +fping_extra_opts | **Optional.** Read extra plugin options from an ini file. ### fping6 @@ -552,6 +559,7 @@ fping_bytes | **Optional.** The size of ICMP packet. fping_target_timeout | **Optional.** The target timeout in milli-seconds. fping_source_ip | **Optional.** The name or ip address of the source ip. fping_source_interface | **Optional.** The source interface name. +fping_extra_opts | **Optional.** Read extra plugin options from an ini file. ### ftp @@ -583,6 +591,7 @@ ftp_ctime | **Optional.** Response time to result in critical status (s ftp_timeout | **Optional.** Seconds before connection times out. Defaults to 10. ftp_ipv4 | **Optional.** Use IPv4 connection. Defaults to false. ftp_ipv6 | **Optional.** Use IPv6 connection. Defaults to false. +ftp_extra_opts | **Optional.** Read extra plugin options from an ini file. ### game @@ -606,6 +615,7 @@ game_mapfield | **Optional.** Field number in raw qstat output that contain game_pingfield | **Optional.** Field number in raw qstat output that contains ping time. game_gametime | **Optional.** Field number in raw qstat output that contains game time. game_hostname | **Optional.** Name of the host running the game. +game_extra_opts | **Optional.** Read extra plugin options from an ini file. ### hostalive @@ -625,6 +635,7 @@ ping_crta | **Optional.** The RTA critical threshold in milliseconds. Defa ping_cpl | **Optional.** The packet loss critical threshold in %. Defaults to 100. ping_packets | **Optional.** The number of packets to send. Defaults to 5. ping_timeout | **Optional.** The plugin timeout in seconds. Defaults to 10. +ping_extra_opts | **Optional.** Read extra plugin options from an ini file. ### hostalive4 @@ -643,6 +654,7 @@ ping_crta | **Optional.** The RTA critical threshold in milliseconds. Defa ping_cpl | **Optional.** The packet loss critical threshold in %. Defaults to 100. ping_packets | **Optional.** The number of packets to send. Defaults to 5. ping_timeout | **Optional.** The plugin timeout in seconds. Defaults to 0 (no timeout). +ping_extra_opts | **Optional.** Read extra plugin options from an ini file. ### hostalive6 @@ -661,6 +673,7 @@ ping_crta | **Optional.** The RTA critical threshold in milliseconds. Defa ping_cpl | **Optional.** The packet loss critical threshold in %. Defaults to 100. ping_packets | **Optional.** The number of packets to send. Defaults to 5. ping_timeout | **Optional.** The plugin timeout in seconds. Defaults to 0 (no timeout). +ping_extra_opts | **Optional.** Read extra plugin options from an ini file. ### hpjd @@ -676,6 +689,7 @@ Name | Description hpjd_address | **Optional.** The host's address. Defaults to "$address$" if the host's `address` attribute is set, "$address6$" otherwise. hpjd_port | **Optional.** The host's SNMP port. Defaults to 161. hpjd_community | **Optional.** The SNMP community. Defaults to "public". +hpjd_extra_opts | **Optional.** Read extra plugin options from an ini file. ### http @@ -737,8 +751,8 @@ http_ipv4 | **Optional.** Use IPv4 connection. Defaults t http_ipv6 | **Optional.** Use IPv6 connection. Defaults to false. http_link | **Optional.** Wrap output in HTML link. Defaults to false. http_verbose | **Optional.** Show details for command-line debugging. Defaults to false. -http_extra_opts | **Optional.** Read extra plugin options from an ini file. http_verify_host | **Optional.** Verify SSL certificate is for the -H hostname (with --sni and -S). Defaults to false. **Only supported by the Nagios plugins version of check\_http, not by the monitoring plugins one.** +http_extra_opts | **Optional.** Read extra plugin options from an ini file. ### curl @@ -827,6 +841,7 @@ icmp_hosts_alive | **Optional.** The number of hosts which have to be alive for icmp_data_bytes | **Optional.** Payload size for each ICMP request. Defaults to 8. icmp_timeout | **Optional.** The plugin timeout in seconds. Defaults to 10 (seconds). icmp_ttl | **Optional.** The TTL on outgoing packets. +icmp_extra_opts | **Optional.** Read extra plugin options from an ini file. ### imap @@ -857,6 +872,7 @@ imap_critical | **Optional.** Response time to result in critical status imap_timeout | **Optional.** Seconds before connection times out (default: 10). imap_ipv4 | **Optional.** Use IPv4 connection. Defaults to false. imap_ipv6 | **Optional.** Use IPv6 connection. Defaults to false. +imap_extra_opts | **Optional.** Read extra plugin options from an ini file. ### ldap @@ -887,6 +903,7 @@ ldap_warning_entries | **Optional.** Number of found entries to result in warnin ldap_critical_entries | **Optional.** Number of found entries to result in critical status. ldap_timeout | **Optional.** Seconds before connection times out (default: 10). ldap_verbose | **Optional.** Show details for command-line debugging (disabled by default) +ldap_extra_opts | **Optional.** Read extra plugin options from an ini file. ### load @@ -904,6 +921,7 @@ load_cload1 | **Optional.** The 1-minute critical threshold. Defaults to 10. load_cload5 | **Optional.** The 5-minute critical threshold. Defaults to 6. load_cload15 | **Optional.** The 15-minute critical threshold. Defaults to 4. load_percpu | **Optional.** Divide the load averages by the number of CPUs (when possible). Defaults to false. +load_extra_opts | **Optional.** Read extra plugin options from an ini file. ### mailq @@ -950,6 +968,7 @@ mysql_cert | **Optional.** Path to SSL certificate. mysql_key | **Optional.** Path to private SSL key. mysql_cadir | **Optional.** Path to CA directory. mysql_ciphers | **Optional.** List of valid SSL ciphers. +mysql_extra_opts | **Optional.** Read extra plugin options from an ini file. ### mysql_query @@ -975,6 +994,7 @@ mysql_query_password | **Optional.** Use the indicated password to authentica mysql_query_execute | **Required.** SQL Query to run on the MySQL Server. mysql_query_warning | **Optional.** Exit with WARNING status if query is outside of the range (format: start:end). mysql_query_critical | **Optional.** Exit with CRITICAL status if query is outside of the range. +mysql_query_extra_opts | **Optional.** Read extra plugin options from an ini file. ### negate @@ -1068,6 +1088,7 @@ ntp_timeoffset | **Optional.** Expected offset of the ntp server relative to lo ntp_timeout | **Optional.** Seconds before connection times out (default: 10). ntp_ipv4 | **Optional.** Use IPv4 connection. Defaults to false. ntp_ipv6 | **Optional.** Use IPv6 connection. Defaults to false. +ntp_extra_opts | **Optional.** Read extra plugin options from an ini file. ### ntp_peer @@ -1095,6 +1116,7 @@ ntp_csource | **Optional.** Critical threshold for number of usable time sou ntp_timeout | **Optional.** Seconds before connection times out (default: 10). ntp_ipv4 | **Optional.** Use IPv4 connection. Defaults to false. ntp_ipv6 | **Optional.** Use IPv6 connection. Defaults to false. +ntp_extra_opts | **Optional.** Read extra plugin options from an ini file. ### pgsql @@ -1122,6 +1144,7 @@ pgsql_timeout | **Optional.** Seconds before connection times out (default: 10) pgsql_query | **Optional.** SQL query to run. Only first column in first row will be read. pgsql_query_warning | **Optional.** SQL query value to result in warning status (double). pgsql_query_critical | **Optional.** SQL query value to result in critical status (double). +pgsql_extra_opts | **Optional.** Read extra plugin options from an ini file. ### ping @@ -1143,6 +1166,7 @@ ping_crta | **Optional.** The RTA critical threshold in milliseconds. Defa ping_cpl | **Optional.** The packet loss critical threshold in %. Defaults to 15. ping_packets | **Optional.** The number of packets to send. Defaults to 5. ping_timeout | **Optional.** The plugin timeout in seconds. Defaults to 0 (no timeout). +ping_extra_opts | **Optional.** Read extra plugin options from an ini file. ### ping4 @@ -1165,6 +1189,7 @@ ping_crta | **Optional.** The RTA critical threshold in milliseconds. Defa ping_cpl | **Optional.** The packet loss critical threshold in %. Defaults to 15. ping_packets | **Optional.** The number of packets to send. Defaults to 5. ping_timeout | **Optional.** The plugin timeout in seconds. Defaults to 0 (no timeout). +ping_extra_opts | **Optional.** Read extra plugin options from an ini file. ### ping6 @@ -1186,6 +1211,7 @@ ping_crta | **Optional.** The RTA critical threshold in milliseconds. Defa ping_cpl | **Optional.** The packet loss critical threshold in %. Defaults to 15. ping_packets | **Optional.** The number of packets to send. Defaults to 5. ping_timeout | **Optional.** The plugin timeout in seconds. Defaults to 0 (no timeout). +ping_extra_opts | **Optional.** Read extra plugin options from an ini file. ### pop @@ -1216,6 +1242,7 @@ pop_critical | **Optional.** Response time to result in critical status pop_timeout | **Optional.** Seconds before connection times out (default: 10). pop_ipv4 | **Optional.** Use IPv4 connection. Defaults to false. pop_ipv6 | **Optional.** Use IPv6 connection. Defaults to false. +pop_extra_opts | **Optional.** Read extra plugin options from an ini file. ### procs @@ -1245,6 +1272,7 @@ procs_argument_regex | **Optional.** Only scan for processes with args that con procs_command | **Optional.** Only scan for exact matches of COMMAND (without path). procs_exclude_process | **Optional.** Exclude processes which match this comma separated list. procs_nokthreads | **Optional.** Only scan for non kernel threads. Defaults to false. +procs_extra_opts | **Optional.** Read extra plugin options from an ini file. ### radius @@ -1274,6 +1302,7 @@ radius_nas_address | **Optional.** The NAS IP address. radius_expect | **Optional.** The response string to expect from the server. radius_retries | **Optional.** The number of times to retry a failed connection. radius_timeout | **Optional.** The number of seconds before connection times out (default: 10). +radius_extra_opts | **Optional.** Read extra plugin options from an ini file. ### rpc @@ -1320,6 +1349,7 @@ simap_critical | **Optional.** Response time to result in critical statu simap_timeout | **Optional.** Seconds before connection times out (default: 10). simap_ipv4 | **Optional.** Use IPv4 connection. Defaults to false. simap_ipv6 | **Optional.** Use IPv6 connection. Defaults to false. +simap_extra_opts | **Optional.** Read extra plugin options from an ini file. ### smart @@ -1328,9 +1358,10 @@ checks a local hard drive with the (Linux specific) SMART interface. Requires in Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -----------------|-------------- -smart_device | **Required.** The name of a local hard drive to monitor. +Name | Description +-----------------|-------------- +smart_device | **Required.** The name of a local hard drive to monitor. +smart_extra_opts | **Optional.** Read extra plugin options from an ini file. ### smtp @@ -1360,6 +1391,7 @@ smtp_critical | **Optional.** Response time to result in critical status smtp_timeout | **Optional.** Seconds before connection times out (default: 10). smtp_ipv4 | **Optional.** Use IPv4 connection. Defaults to false. smtp_ipv6 | **Optional.** Use IPv6 connection. Defaults to false. +smtp_extra_opts | **Optional.** Read extra plugin options from an ini file. ### snmp @@ -1396,6 +1428,7 @@ snmp_timeout | **Optional.** The command timeout in seconds. Defaults to snmp_offset | **Optional.** Add/subtract the specified OFFSET to numeric sensor data. snmp_output_delimiter | **Optional.** Separates output on multiple OID requests. snmp_perf_oids | **Optional.** Label performance data with OIDs instead of --label's. +snmp_extra_opts | **Optional.** Read extra plugin options from an ini file. ### snmpv3 @@ -1428,6 +1461,7 @@ snmp3_multiplier |**Optional.** Multiplies current value, 0 < n < 1 works as snmpv3_rate_multiplier | **Optional.** Converts rate per second. For example, set to 60 to convert to per minute. snmpv3_rate | **Optional.** Boolean. Enable rate calculation. snmpv3_timeout | **Optional.** The command timeout in seconds. Defaults to 10 seconds. +snmpv3_extra_opts | **Optional.** Read extra plugin options from an ini file. ### snmp-uptime @@ -1441,6 +1475,7 @@ Name | Description snmp_address | **Optional.** The host's address. Defaults to "$address$" if the host's `address` attribute is set, "$address6$" otherwise. snmp_oid | **Optional.** The SNMP OID. Defaults to "1.3.6.1.2.1.1.3.0". snmp_community | **Optional.** The SNMP community. Defaults to "public". +snmp_extra_opts | **Optional.** Read extra plugin options from an ini file. ### spop @@ -1471,6 +1506,7 @@ spop_critical | **Optional.** Response time to result in critical status spop_timeout | **Optional.** Seconds before connection times out (default: 10). spop_ipv4 | **Optional.** Use IPv4 connection. Defaults to false. spop_ipv6 | **Optional.** Use IPv6 connection. Defaults to false. +spop_extra_opts | **Optional.** Read extra plugin options from an ini file. ### ssh @@ -1489,6 +1525,7 @@ ssh_ipv4 | **Optional.** Use IPv4 connection. Defaults to false. ssh_ipv6 | **Optional.** Use IPv6 connection. Defaults to false. ssh_remote_version | **Optional.** Alert if string doesn't match expected server version (ex: OpenSSH_3.9p1). ssh_remote_protocol | **Optional.** Alert if protocol doesn't match expected protocol version (ex: 2.0). +ssh_extra_opts | **Optional.** Read extra plugin options from an ini file. ### ssl @@ -1506,6 +1543,7 @@ ssl_timeout | **Optional.** Timeout in seconds for the connect ssl_cert_valid_days_warn | **Optional.** Warning threshold for days before the certificate will expire. When used, the default for ssl_cert_valid_days_critical is 0. ssl_cert_valid_days_critical | **Optional.** Critical threshold for days before the certificate will expire. When used, ssl_cert_valid_days_warn must also be set. ssl_sni | **Optional.** The `server_name` that is sent to select the SSL certificate to check. Important if SNI is used. +ssl_extra_opts | **Optional.** Read extra plugin options from an ini file. ### ssmtp @@ -1536,6 +1574,7 @@ ssmtp_critical | **Optional.** Response time to result in critical statu ssmtp_timeout | **Optional.** Seconds before connection times out (default: 10). ssmtp_ipv4 | **Optional.** Use IPv4 connection. Defaults to false. ssmtp_ipv6 | **Optional.** Use IPv6 connection. Defaults to false. +ssmtp_extra_opts | **Optional.** Read extra plugin options from an ini file. ### swap @@ -1552,6 +1591,7 @@ swap_cfree | **Optional.** The free swap space critical threshold in % (ena swap_integer | **Optional.** Specifies whether the thresholds are passed as number or percent value. Defaults to false (percent values). swap_allswaps | **Optional.** Conduct comparisons for all swap partitions, one by one. Defaults to false. swap_noswap | **Optional.** Resulting state when there is no swap regardless of thresholds. Possible values are "ok", "warning", "critical", "unknown". Defaults to "critical". +swap_extra_opts | **Optional.** Read extra plugin options from an ini file. ### tcp @@ -1584,6 +1624,7 @@ tcp_ctime | **Optional.** Response time to result in critical status (seco tcp_timeout | **Optional.** Seconds before connection times out. Defaults to 10. tcp_ipv4 | **Optional.** Use IPv4 connection. Defaults to false. tcp_ipv6 | **Optional.** Use IPv6 connection. Defaults to false. +tcp_extra_opts | **Optional.** Read extra plugin options from an ini file. ### udp @@ -1602,6 +1643,7 @@ udp_expect | **Required.** The payload to expect in the response datagram. udp_quit | **Optional.** The payload to send to 'close' the session. udp_ipv4 | **Optional.** Use IPv4 connection. Defaults to false. udp_ipv6 | **Optional.** Use IPv6 connection. Defaults to false. +udp_extra_opts | **Optional.** Read extra plugin options from an ini file. ### ups @@ -1622,6 +1664,7 @@ ups_warning | **Optional.** The warning threshold for the selected variable. ups_critical | **Optional.** The critical threshold for the selected variable. ups_celsius | **Optional.** Display the temperature in degrees Celsius instead of Fahrenheit. Defaults to `false`. ups_timeout | **Optional.** The number of seconds before the connection times out. Defaults to 10. +ups_extra_opts | **Optional.** Read extra plugin options from an ini file. ### users @@ -1632,10 +1675,11 @@ error if the number exceeds the thresholds specified. Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -----------------|-------------- -users_wgreater | **Optional.** The user count warning threshold. Defaults to 20. -users_cgreater | **Optional.** The user count critical threshold. Defaults to 50. +Name | Description +-----------------|-------------- +users_wgreater | **Optional.** The user count warning threshold. Defaults to 20. +users_cgreater | **Optional.** The user count critical threshold. Defaults to 50. +users_extra_opts | **Optional.** Read extra plugin options from an ini file. ### uptime diff --git a/itl/command-plugins.conf b/itl/command-plugins.conf index 7563801f9..e3eb5e370 100644 --- a/itl/command-plugins.conf +++ b/itl/command-plugins.conf @@ -24,6 +24,10 @@ template CheckCommand "ping-common" { value = "$ping_address$" description = "host to ping" } + "--extra-opts" = { + value = "$ping_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-w" = { value = "$ping_wrta$,$ping_wpl$%" description = "warning threshold pair" @@ -101,6 +105,10 @@ template CheckCommand "fping-common" { ] arguments = { + "--extra-opts" = { + value = "$fping_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-w" = { value = "$fping_wrta$,$fping_wpl$%" description = "warning threshold pair" @@ -169,6 +177,10 @@ object CheckCommand "tcp" { value = "$tcp_address$" description = "Host name, IP Address, or unix socket (must be an absolute path)." } + "--extra-opts" = { + value = "$tcp_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-p" = { value = "$tcp_port$" description = "The TCP port number." @@ -276,6 +288,10 @@ object CheckCommand "ssl" { value = "$ssl_address$" description = "Host address" } + "--extra-opts" = { + value = "$ssl_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-p" = { value = "$ssl_port$" description ="TCP port (default: 443)" @@ -321,6 +337,10 @@ object CheckCommand "udp" { ] arguments = { + "--extra-opts" = { + value = "$udp_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-s" = { value = "$udp_send$" required = true @@ -782,6 +802,10 @@ object CheckCommand "ftp" { value = "$ftp_address$" description = "The host's address. Defaults to $address$ or $address6$ if the address attribute is not set." } + "--extra-opts" = { + value = "$ftp_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-p" = { value = "$ftp_port$" description = "The FTP port number. Defaults to none" @@ -885,6 +909,10 @@ object CheckCommand "smtp" { value = "$smtp_address$" description = "Host name, IP Address, or unix socket (must be an absolute path)" } + "--extra-opts" = { + value = "$smtp_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-p" = { value = "$smtp_port$" description = "Port number (default: 25)" @@ -970,6 +998,10 @@ object CheckCommand "ssmtp" { value = "$ssmtp_address$" description = "Host name, IP Address, or unix socket (must be an absolute path)" } + "--extra-opts" = { + value = "$ssmtp_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-p" = { value = "$ssmtp_port$" description = "Port number (default: none)" @@ -1059,6 +1091,10 @@ object CheckCommand "imap" { value = "$imap_address$" description = "Host name, IP Address, or unix socket (must be an absolute path)" } + "--extra-opts" = { + value = "$imap_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-p" = { value = "$imap_port$" description = "Port number (default: none)" @@ -1148,6 +1184,10 @@ object CheckCommand "simap" { value = "$simap_address$" description = "Host name, IP Address, or unix socket (must be an absolute path)" } + "--extra-opts" = { + value = "$simap_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-p" = { value = "$simap_port$" description = "Port number (default: none)" @@ -1237,6 +1277,10 @@ object CheckCommand "pop" { value = "$pop_address$" description = "Host name, IP Address, or unix socket (must be an absolute path)" } + "--extra-opts" = { + value = "$pop_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-p" = { value = "$pop_port$" description = "Port number (default: none)" @@ -1326,6 +1370,10 @@ object CheckCommand "spop" { value = "$spop_address$" description = "Host name, IP Address, or unix socket (must be an absolute path)" } + "--extra-opts" = { + value = "$spop_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-p" = { value = "$spop_port$" description = "Port number (default: none)" @@ -1415,6 +1463,10 @@ object CheckCommand "ntp_time" { value = "$ntp_address$" description = "Host name, IP Address, or unix socket (must be an absolute path)" } + "--extra-opts" = { + value = "$ntp_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-p" = { value = "$ntp_port$" description = "Port number (default: 123)" @@ -1464,6 +1516,10 @@ object CheckCommand "ntp_peer" { value = "$ntp_address$" description = "Host name, IP Address, or unix socket (must be an absolute path)" } + "--extra-opts" = { + value = "$ntp_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-p" = { value = "$ntp_port$" description = "Port number (default: 123)" @@ -1529,6 +1585,10 @@ object CheckCommand "ssh" { command = [ PluginDir + "/check_ssh" ] arguments = { + "--extra-opts" = { + value = "$ssh_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-p" = { value = "$ssh_port$" description = "Port number (default: 22)" @@ -1569,6 +1629,10 @@ object CheckCommand "disk" { command = [ PluginDir + "/check_disk" ] arguments = { + "--extra-opts" = { + value = "$disk_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-w" = { value = "$disk_wfree$" description = "Exit with WARNING status if less than INTEGER units of disk are free or Exit with WARNING status if less than PERCENT of disk space is free" @@ -1776,6 +1840,10 @@ object CheckCommand "users" { command = [ PluginDir + "/check_users" ] arguments = { + "--extra-opts" = { + value = "$users_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-w" = { value = "$users_wgreater$" description = "Set WARNING status if more than INTEGER users are logged in" @@ -1794,6 +1862,10 @@ object CheckCommand "procs" { command = [ PluginDir + "/check_procs" ] arguments = { + "--extra-opts" = { + value = "$procs_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-w" = { value = "$procs_warning$" description = "Generate warning state if metric is outside this range" @@ -1870,6 +1942,10 @@ object CheckCommand "swap" { command = [ PluginDir + "/check_swap" ] arguments = { + "--extra-opts" = { + value = "$swap_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-w" = {{ if (macro("$swap_integer$")) { return macro("$swap_wfree$") @@ -1904,6 +1980,10 @@ object CheckCommand "load" { command = [ PluginDir + "/check_load" ] arguments = { + "--extra-opts" = { + value = "$load_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-w" = { value = "$load_wload1$,$load_wload5$,$load_wload15$" description = "Exit with WARNING status if load average exceeds WLOADn" @@ -1937,6 +2017,10 @@ object CheckCommand "snmp" { value = "$snmp_address$" description = "Host name, IP Address, or unix socket (must be an absolute path)" } + "--extra-opts" = { + value = "$snmp_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-o" = { value = "$snmp_oid$" description = "Object identifier(s) or SNMP variables whose value you wish to query" @@ -2053,6 +2137,10 @@ object CheckCommand "snmpv3" { value = "$snmpv3_address$" description = "Host name, IP Address, or unix socket (must be an absolute path)" } + "--extra-opts" = { + value = "$snmpv3_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-p" = { value = "$snmpv3_port$" description = "Port number" @@ -2238,6 +2326,10 @@ object CheckCommand "dhcp" { command = [ PluginDir + "/check_dhcp" ] arguments = { + "--extra-opts" = { + value = "$dhcp_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-s" = { value = "$dhcp_serverip$" description = "IP address of DHCP server that we must hear from" @@ -2277,6 +2369,10 @@ object CheckCommand "dns" { value = "$dns_lookup$" description = "The name or address you want to query." } + "--extra-opts" = { + value = "$dns_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-s" = { value = "$dns_server$" description = "Optional DNS server you want to use for the lookup." @@ -2329,6 +2425,10 @@ object CheckCommand "dig" { value = "$dig_server$" description = "Host name, IP Address, or unix socket (must be an absolute path)" } + "--extra-opts" = { + value = "$dig_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-p" = { value = "$dig_port$" description = "Port number (default: 53)" @@ -2387,6 +2487,10 @@ object CheckCommand "nscp" { value = "$nscp_address$" description = "Name of the host to check" } + "--extra-opts" = { + value = "$nscp_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-p" = { value = "$nscp_port$" description = "Optional port number (default: 1248)" @@ -2438,6 +2542,10 @@ object CheckCommand "by_ssh" { value = "$by_ssh_address$" description = "Host name, IP Address, or unix socket (must be an absolute path)" } + "--extra-opts" = { + value = "$by_ssh_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-p" = { value = "$by_ssh_port$" description = "Port number (default: none)" @@ -2515,6 +2623,10 @@ object CheckCommand "ups" { description = "Address of the upsd server" required = true } + "--extra-opts" = { + value = "$ups_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-u" = { value = "$ups_name$" description = "Name of the UPS to monitor" @@ -2652,6 +2764,10 @@ object CheckCommand "hpjd" { value = "$hpjd_address$" description = "Host address" } + "--extra-opts" = { + value = "$hpjd_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-C" = { value = "$hpjd_community$" description = "The SNMP community name (default=public)" @@ -2675,6 +2791,10 @@ object CheckCommand "icmp" { order = 1 description = "Host address" } + "--extra-opts" = { + value = "$icmp_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-w" = { value = "$icmp_wrta$,$icmp_wpl$%" description = "warning threshold (currently 200.000ms,10%)" @@ -2734,6 +2854,10 @@ object CheckCommand "ldap" { value = "$ldap_address$" description = "Host name, IP Address, or unix socket (must be an absolute path)" } + "--extra-opts" = { + value = "$ldap_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-p" = { value = "$ldap_port$" description = "Port number (default: 389)" @@ -2813,6 +2937,10 @@ object CheckCommand "clamd" { description = "The host's address or unix socket (must be an absolute path)." required = true } + "--extra-opts" = { + value = "$clamd_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-p" = { value = "$clamd_port$" description = "Port number (default: none)." @@ -2957,6 +3085,10 @@ object CheckCommand "pgsql" { value = "$pgsql_hostname$" description = "Host name, IP Address, or unix socket (must be an absolute path)" } + "--extra-opts" = { + value = "$pgsql_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-P" = { value = "$pgsql_port$" description = "Port number (default: 5432)" @@ -3021,6 +3153,10 @@ object CheckCommand "mysql" { value = "$mysql_hostname$" description = "Host name, IP Address, or unix socket (must be an absolute path)" } + "--extra-opts" = { + value = "$mysql_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-P" = { value = "$mysql_port$" description = "Port number (default: 3306)" @@ -3182,6 +3318,10 @@ object CheckCommand "smart" { command = [ PluginDir + "/check_ide_smart" ] arguments = { + "--extra-opts" = { + value = "$smart_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-d" = { value = "$smart_device$" description = "Name of a local hard drive to monitor" @@ -3244,6 +3384,10 @@ object CheckCommand "game" { command = [ PluginDir + "/check_game" ] arguments = { + "--extra-opts" = { + value = "$game_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-P" = { value = "$game_port$" description = "Port to connect to" @@ -3297,6 +3441,10 @@ object CheckCommand "mysql_query" { value = "$mysql_query_hostname$" description = "Host name, IP Address, or unix socket (must be an absolute path)" } + "--extra-opts" = { + value = "$mysql_query_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-P" = { value = "$mysql_query_port$" description = "Port number (default: 3306)" @@ -3350,6 +3498,10 @@ object CheckCommand "radius" { value = "$radius_address$", description = "Host name, IP Address, or unix socket (must be an absolute path)" } + "--extra-opts" = { + value = "$radius_extra_opts$" + description = "Read extra plugin options from an ini file." + } "-F" = { value = "$radius_config_file$", description = "Configuration file" From e18c923abbf6e7bdaa3b035178418d8673bd7f41 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 12 Aug 2020 11:34:28 +0200 Subject: [PATCH 148/415] GET /v1/objects/*: handle "attrs":[] as expected ... i.e. yield no attrs and not all. refs #8167 --- lib/remote/objectqueryhandler.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/remote/objectqueryhandler.cpp b/lib/remote/objectqueryhandler.cpp index 8b7313789..06d168b8a 100644 --- a/lib/remote/objectqueryhandler.cpp +++ b/lib/remote/objectqueryhandler.cpp @@ -31,7 +31,7 @@ Dictionary::Ptr ObjectQueryHandler::SerializeObjectAttrs(const Object::Ptr& obje } } - if (!isJoin && (!attrs || attrs->GetLength() == 0)) + if (!isJoin && !attrs) allAttrs = true; if (allAttrs) { From 4175a4731490896ad96f19553cb360a175abce0d Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 21 Jan 2025 17:50:39 +0100 Subject: [PATCH 149/415] tools/win32/configure*.ps1: allow custom $CMAKE_ARGS (JSON array) --- tools/win32/configure-dev.ps1 | 6 +++++- tools/win32/configure.ps1 | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/tools/win32/configure-dev.ps1 b/tools/win32/configure-dev.ps1 index 12cc004c8..a2caeb201 100644 --- a/tools/win32/configure-dev.ps1 +++ b/tools/win32/configure-dev.ps1 @@ -27,6 +27,9 @@ if (-not (Test-Path env:CMAKE_GENERATOR)) { if (-not (Test-Path env:CMAKE_GENERATOR_PLATFORM)) { $env:CMAKE_GENERATOR_PLATFORM = 'x64' } +if (-not (Test-Path env:CMAKE_ARGS)) { + $env:CMAKE_ARGS = '[]' +} if (-not (Test-Path env:OPENSSL_ROOT_DIR)) { $env:OPENSSL_ROOT_DIR = 'c:\local\OpenSSL-Win64' } @@ -60,7 +63,8 @@ if (Test-Path CMakeCache.txt) { -DBOOST_LIBRARYDIR="$env:BOOST_LIBRARYDIR" ` -DBOOST_INCLUDEDIR="$env:BOOST_ROOT" ` -DFLEX_EXECUTABLE="$env:FLEX_BINARY" ` - -DBISON_EXECUTABLE="$env:BISON_BINARY" + -DBISON_EXECUTABLE="$env:BISON_BINARY" ` + $(ConvertFrom-Json -InputObject "$env:CMAKE_ARGS") cd "$sourcePath" diff --git a/tools/win32/configure.ps1 b/tools/win32/configure.ps1 index 9871d0e8e..c1ca90454 100644 --- a/tools/win32/configure.ps1 +++ b/tools/win32/configure.ps1 @@ -29,6 +29,9 @@ if (-not (Test-Path env:CMAKE_GENERATOR_PLATFORM)) { $env:CMAKE_GENERATOR_PLATFORM = 'x64' } } +if (-not (Test-Path env:CMAKE_ARGS)) { + $env:CMAKE_ARGS = '[]' +} if (-not (Test-Path env:OPENSSL_ROOT_DIR)) { $env:OPENSSL_ROOT_DIR = "c:\local\OpenSSL_3_0_14-Win${env:BITS}" } @@ -63,7 +66,8 @@ if (Test-Path CMakeCache.txt) { -DBOOST_LIBRARYDIR="$env:BOOST_LIBRARYDIR" ` -DBOOST_INCLUDEDIR="$env:BOOST_ROOT" ` -DFLEX_EXECUTABLE="$env:FLEX_BINARY" ` - -DBISON_EXECUTABLE="$env:BISON_BINARY" + -DBISON_EXECUTABLE="$env:BISON_BINARY" ` + $(ConvertFrom-Json -InputObject "$env:CMAKE_ARGS") cd "$sourcePath" From 263957937432b3192f25d1aa7c271e0002ba32ce Mon Sep 17 00:00:00 2001 From: Blerim Sheqa Date: Thu, 23 Jan 2025 09:27:17 +0100 Subject: [PATCH 150/415] Include Nagios in the migration docs --- doc/23-migrating-from-icinga-1x.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/23-migrating-from-icinga-1x.md b/doc/23-migrating-from-icinga-1x.md index 14a30cf2e..22954b557 100644 --- a/doc/23-migrating-from-icinga-1x.md +++ b/doc/23-migrating-from-icinga-1x.md @@ -1,4 +1,8 @@ -# Migration from Icinga 1.x +# Migration from Icinga 1.x or Nagios + +!!! note + + Icinga 1.x was originally a fork of Nagios. The information provided here also applies to Nagios. ## Configuration Migration From 7d12c1a52471b681a14ced0591e44013dd0e73f7 Mon Sep 17 00:00:00 2001 From: Sebastian Grund Date: Mon, 23 Sep 2024 10:50:08 +0200 Subject: [PATCH 151/415] Add tags functionality to `ElasticSearchWriter` --- lib/perfdata/elasticsearchwriter.cpp | 80 ++++++++++++++++++++++++++++ lib/perfdata/elasticsearchwriter.hpp | 4 ++ lib/perfdata/elasticsearchwriter.ti | 18 +++++++ 3 files changed, 102 insertions(+) diff --git a/lib/perfdata/elasticsearchwriter.cpp b/lib/perfdata/elasticsearchwriter.cpp index 9fb2aa90f..eaa19da59 100644 --- a/lib/perfdata/elasticsearchwriter.cpp +++ b/lib/perfdata/elasticsearchwriter.cpp @@ -5,6 +5,7 @@ #include "remote/url.hpp" #include "icinga/compatutility.hpp" #include "icinga/service.hpp" +#include "icinga/macroprocessor.hpp" #include "icinga/checkcommand.hpp" #include "base/application.hpp" #include "base/defer.hpp" @@ -131,6 +132,33 @@ void ElasticsearchWriter::Pause() ObjectImpl::Pause(); } +void ElasticsearchWriter::AddTemplateTags(const Dictionary::Ptr& fields, const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) +{ + Host::Ptr host; + Service::Ptr service; + tie(host, service) = GetHostService(checkable); + + Dictionary::Ptr tmpl = service ? GetServiceTagsTemplate() : GetHostTagsTemplate(); + + if (tmpl) { + MacroProcessor::ResolverList resolvers; + resolvers.emplace_back("host", host); + if (service) { + resolvers.emplace_back("service", service); + } + + ObjectLock olock(tmpl); + for (const Dictionary::Pair& pair : tmpl) { + String missingMacro; + Value value = MacroProcessor::ResolveMacros(pair.second, resolvers, cr, &missingMacro); + + if (missingMacro.IsEmpty()) { + fields->Set(pair.first, value); + } + } + } +} + void ElasticsearchWriter::AddCheckResult(const Dictionary::Ptr& fields, const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) { String prefix = "check_result."; @@ -257,6 +285,8 @@ void ElasticsearchWriter::InternalCheckResultHandler(const Checkable::Ptr& check ts = cr->GetExecutionEnd(); } + AddTemplateTags(fields, checkable, cr); + Enqueue(checkable, "checkresult", fields, ts); } @@ -307,6 +337,8 @@ void ElasticsearchWriter::StateChangeHandlerInternal(const Checkable::Ptr& check ts = cr->GetExecutionEnd(); } + AddTemplateTags(fields, checkable, cr); + Enqueue(checkable, "statechange", fields, ts); } @@ -377,6 +409,8 @@ void ElasticsearchWriter::NotificationSentToAllUsersHandlerInternal(const Notifi ts = cr->GetExecutionEnd(); } + AddTemplateTags(fields, checkable, cr); + Enqueue(checkable, "notification", fields, ts); } @@ -683,3 +717,49 @@ String ElasticsearchWriter::FormatTimestamp(double ts) return Utility::FormatDateTime("%Y-%m-%dT%H:%M:%S", ts) + "." + Convert::ToString(milliSeconds) + Utility::FormatDateTime("%z", ts); } + +void ElasticsearchWriter::ValidateHostTagsTemplate(const Lazy& lvalue, const ValidationUtils& utils) +{ + ObjectImpl::ValidateHostTagsTemplate(lvalue, utils); + + Dictionary::Ptr tags = lvalue(); + if (tags) { + ObjectLock olock(tags); + for (const Dictionary::Pair& pair : tags) { + if (pair.second.IsObjectType()) { + Array::Ptr arrObject = pair.second; + ObjectLock arrLock(arrObject); + for (const Value& arrValue : arrObject) { + if (!MacroProcessor::ValidateMacroString(arrValue)) { + BOOST_THROW_EXCEPTION(ValidationError(this, { "host_tags_template", pair.first }, "Closing $ not found in macro format string '" + arrValue + "'.")); + } + } + } else if (!MacroProcessor::ValidateMacroString(pair.second)) { + BOOST_THROW_EXCEPTION(ValidationError(this, { "host_tags_template", pair.first }, "Closing $ not found in macro format string '" + pair.second + "'.")); + } + } + } +} + +void ElasticsearchWriter::ValidateServiceTagsTemplate(const Lazy& lvalue, const ValidationUtils& utils) +{ + ObjectImpl::ValidateServiceTagsTemplate(lvalue, utils); + + Dictionary::Ptr tags = lvalue(); + if (tags) { + ObjectLock olock(tags); + for (const Dictionary::Pair& pair : tags) { + if (pair.second.IsObjectType()) { + Array::Ptr arrObject = pair.second; + ObjectLock arrLock(arrObject); + for (const Value& arrValue : arrObject) { + if (!MacroProcessor::ValidateMacroString(arrValue)) { + BOOST_THROW_EXCEPTION(ValidationError(this, { "service_tags_template", pair.first }, "Closing $ not found in macro format string '" + arrValue + "'.")); + } + } + } else if (!MacroProcessor::ValidateMacroString(pair.second)) { + BOOST_THROW_EXCEPTION(ValidationError(this, { "service_tags_template", pair.first }, "Closing $ not found in macro format string '" + pair.second + "'.")); + } + } + } +} diff --git a/lib/perfdata/elasticsearchwriter.hpp b/lib/perfdata/elasticsearchwriter.hpp index a988094d8..9ecd0fd76 100644 --- a/lib/perfdata/elasticsearchwriter.hpp +++ b/lib/perfdata/elasticsearchwriter.hpp @@ -23,6 +23,9 @@ public: static String FormatTimestamp(double ts); + void ValidateHostTagsTemplate(const Lazy &lvalue, const ValidationUtils &utils) override; + void ValidateServiceTagsTemplate(const Lazy &lvalue, const ValidationUtils &utils) override; + protected: void OnConfigLoaded() override; void Resume() override; @@ -37,6 +40,7 @@ private: std::mutex m_DataBufferMutex; void AddCheckResult(const Dictionary::Ptr& fields, const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); + void AddTemplateTags(const Dictionary::Ptr& fields, const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); void StateChangeHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, StateType type); void StateChangeHandlerInternal(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, StateType type); diff --git a/lib/perfdata/elasticsearchwriter.ti b/lib/perfdata/elasticsearchwriter.ti index e3b8e27f5..ed9b24fb5 100644 --- a/lib/perfdata/elasticsearchwriter.ti +++ b/lib/perfdata/elasticsearchwriter.ti @@ -29,6 +29,9 @@ class ElasticsearchWriter : ConfigObject [config] bool enable_tls { default {{{ return false; }}} }; + + [config] Dictionary::Ptr host_tags_template; + [config] Dictionary::Ptr service_tags_template; [config] bool insecure_noverify { default {{{ return false; }}} }; @@ -47,4 +50,19 @@ class ElasticsearchWriter : ConfigObject }; }; +validator ElasticsearchWriter { + Dictionary host_tags_template { + String "*"; + Array "*" { + String "*"; + }; + }; + Dictionary service_tags_template { + String "*"; + Array "*" { + String "*"; + }; + }; +}; + } From 7dc396ce8a287cc9376912194a1244735c30b985 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Fri, 24 Jan 2025 10:16:44 +0100 Subject: [PATCH 152/415] CHANGELOG.md: add v2.13.11 --- CHANGELOG.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9b9d08ad..62a295194 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -243,6 +243,35 @@ Add `linux_netdev` check command. #9045 * Several code quality improvements. #8815 #9106 #9250 #9508 #9517 #9537 #9594 #9605 #9606 #9641 #9658 #9702 #9717 #9738 +## 2.13.11 (2025-01-23) + +This bugfix release addresses several crashes, +both in the core itself and in Icinga DB (numbers out of range). +In addition, it fixes several other issues such as lost notifications +or TimePeriod/ScheduledDowntime exceeding specified date ranges. + +### Crash Fixes + +* Invalid `DateTime#format()` arguments in config and console on Windows Server 2016 and older. #10165 +* Downtime scheduling at runtime with non-existent trigger. #10127 +* Object creation at runtime during Icinga DB initialization. #10164 +* Icinga DB: several numbers out of database schema range. #10244 + +### Miscellaneous Bugfixes + +* Lost notifications after recovery outside the notification time period. #10241 +* TimePeriod/ScheduledDowntime exceeding specified date range. #10128 #10133 +* Make parallel config syncs more robust. #10126 +* Reduce unnecessary cluster messages setting the next check time. #10168 + +### Windows + +* Update OpenSSL shipped on Windows to v3.0.15. #10175 +* Update Boost shipped on Windows to v1.86. #10134 +* Support CMake v3.29. #10087 +* Don't require to build .msi as admin. #10305 +* Build configuration scripts: allow custom `$CMAKE_ARGS`. #10315 + ## 2.13.10 (2024-11-12) This security release fixes a TLS certificate validation bypass. From a848d360ac19ff7b99e5b29170599bc8ba51f0de Mon Sep 17 00:00:00 2001 From: Sebastian Grund Date: Mon, 23 Sep 2024 10:54:47 +0200 Subject: [PATCH 153/415] Documentation for tags fuctionality in `ElasticSearchWriter` --- doc/09-object-types.md | 8 +++++++- doc/14-features.md | 22 ++++++++++++++++++++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/doc/09-object-types.md b/doc/09-object-types.md index c2d5e5eff..67735e986 100644 --- a/doc/09-object-types.md +++ b/doc/09-object-types.md @@ -1181,7 +1181,7 @@ Configuration Attributes: ### ElasticsearchWriter -Writes check result metrics and performance data to an Elasticsearch instance. +Writes check result metrics and performance data to an Elasticsearch or OpenSearch instance. This configuration object is available as [elasticsearch feature](14-features.md#elasticsearch-writer). Example: @@ -1194,6 +1194,10 @@ object ElasticsearchWriter "elasticsearch" { enable_send_perfdata = true + host_tags_template = { + os_name = "$host.vars.os$" + } + flush_threshold = 1024 flush_interval = 10 } @@ -1215,6 +1219,8 @@ Configuration Attributes: password | String | **Optional.** Basic auth password if Elasticsearch is hidden behind an HTTP proxy. enable\_tls | Boolean | **Optional.** Whether to use a TLS stream. Defaults to `false`. Requires an HTTP proxy. insecure\_noverify | Boolean | **Optional.** Disable TLS peer verification. + host\_tags\_template | Dictionary | **Optional.** Allows to apply additional tags to the Elasticsearch host entries. + service\_tags\_template | Dictionary | **Optional.** Allows to apply additional tags to the Elasticsearch service entries. ca\_path | String | **Optional.** Path to CA certificate to validate the remote host. Requires `enable_tls` set to `true`. cert\_path | String | **Optional.** Path to host certificate to present to the remote host for mutual verification. Requires `enable_tls` set to `true`. key\_path | String | **Optional.** Path to host key to accompany the cert\_path. Requires `enable_tls` set to `true`. diff --git a/doc/14-features.md b/doc/14-features.md index 6ad4d1095..f0981b350 100644 --- a/doc/14-features.md +++ b/doc/14-features.md @@ -398,6 +398,28 @@ check_result.perfdata..warn check_result.perfdata..crit ``` +Additionaly it is possible to configure custom tags that are applied to the metrics via `host_tags_template` or `service_tags_template`. +Depending on whether the write event was triggered on a service or host object, additional tags are added to the ElasticSearch entries. + +A host metrics entry configured with the following `host_tags_template`: + +``` +host_tags_template = { + + os_name = "$host.vars.os$" + custom_label = "A Custom Label" + list = [ "$host.groups$", "$host.vars.foo$" ] +} +``` + +Will in addition to the above mentioned lines also contain: + +``` +os_name = "Linux" +custom_label = "A Custom Label" +list = [ "group-A;linux-servers", "bar" ] +``` + #### Elasticsearch in Cluster HA Zones The Elasticsearch feature supports [high availability](06-distributed-monitoring.md#distributed-monitoring-high-availability-features) From 54cb29ec532f87d6a98615eb85e97d50cf328f50 Mon Sep 17 00:00:00 2001 From: Sebastian Grund Date: Wed, 25 Sep 2024 15:36:45 +0200 Subject: [PATCH 154/415] doc/14-features.md: correct Elasticsearch versions --- doc/14-features.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/doc/14-features.md b/doc/14-features.md index f0981b350..31c696619 100644 --- a/doc/14-features.md +++ b/doc/14-features.md @@ -335,16 +335,14 @@ More integrations: #### Elasticsearch Writer This feature forwards check results, state changes and notification events -to an [Elasticsearch](https://www.elastic.co/products/elasticsearch) installation over its HTTP API. +to an [Elasticsearch](https://www.elastic.co/products/elasticsearch) or an [OpenSearch](https://opensearch.org/) installation over its HTTP API. The check results include parsed performance data metrics if enabled. > **Note** > -> Elasticsearch 5.x or 6.x are required. This feature has been successfully tested with -> Elasticsearch 5.6.7 and 6.3.1. - - +> Elasticsearch 7.x, 8.x or Opensearch 2.12.x are required. This feature has been successfully tested with +> Elasticsearch 7.17.10, 8.8.1 and OpenSearch 2.13.0. Enable the feature and restart Icinga 2. From 411c57aac5860859290d0f5057db0f6b59d0cf65 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 8 Nov 2024 16:42:11 +0100 Subject: [PATCH 155/415] API: also log error behind "No data received on new API connection" --- lib/remote/apilistener.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 519469aaf..5863e52e8 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -793,12 +793,12 @@ void ApiListener::NewClientHandlerInternal( if (client->async_fill(yc[ec]) == 0u) { if (identity.IsEmpty()) { Log(LogInformation, "ApiListener") - << "No data received on new API connection " << conninfo << ". " - << "Ensure that the remote endpoints are properly configured in a cluster setup."; + << "No data received on new API connection " << conninfo << ": " << ec.message() + << ". Ensure that the remote endpoints are properly configured in a cluster setup."; } else { Log(LogWarning, "ApiListener") - << "No data received on new API connection " << conninfo << " for identity '" << identity << "'. " - << "Ensure that the remote endpoints are properly configured in a cluster setup."; + << "No data received on new API connection " << conninfo << " for identity '" << identity << "': " << ec.message() + << ". Ensure that the remote endpoints are properly configured in a cluster setup."; } return; From 275753e49b6af28dac035214204c331573a42147 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 24 Jan 2025 11:31:40 +0100 Subject: [PATCH 156/415] docs: Document `mail-{host,service}-notification` `-X` option --- doc/03-monitoring-basics.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/03-monitoring-basics.md b/doc/03-monitoring-basics.md index 484dc5db8..92cfc8854 100644 --- a/doc/03-monitoring-basics.md +++ b/doc/03-monitoring-basics.md @@ -2579,6 +2579,7 @@ information. `notification_useremail` | **Required.** The notification's recipient(s). Defaults to `$user.email$`. `notification_hoststate` | **Required.** Current state of host. Defaults to `$host.state$`. `notification_type` | **Required.** Type of notification. Defaults to `$notification.type$`. + `notification_hostnotes` | **Optional.** The host's notes. Defaults to `$host.notes$`. `notification_address` | **Optional.** The host's IPv4 address. Defaults to `$address$`. `notification_address6` | **Optional.** The host's IPv6 address. Defaults to `$address6$`. `notification_author` | **Optional.** Comment author. Defaults to `$notification.author$`. @@ -2607,6 +2608,8 @@ information. `notification_useremail` | **Required.** The notification's recipient(s). Defaults to `$user.email$`. `notification_servicestate` | **Required.** Current state of host. Defaults to `$service.state$`. `notification_type` | **Required.** Type of notification. Defaults to `$notification.type$`. + `notification_hostnotes` | **Optional.** The host's notes. Defaults to `$host.notes$`. + `notification_servicenotes` | **Optional.** The service's notes. Defaults to `$service.notes$`. `notification_address` | **Optional.** The host's IPv4 address. Defaults to `$address$`. `notification_address6` | **Optional.** The host's IPv6 address. Defaults to `$address6$`. `notification_author` | **Optional.** Comment author. Defaults to `$notification.author$`. From 1400ccd4306b38a54da49c8cfbd2249c809e6c6c Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Fri, 24 Jan 2025 11:52:24 +0100 Subject: [PATCH 157/415] Update Icinga 2 CLI Commands Documentation First, the "--help" output from some sections was quite outdated, sometimes manually patched. This resulted in incorrect and inconsistent information, addressed by regenerating the output for each section based on a current Icinga 2 version 2.14.4. The two subsections about "--app" and "--library" were removed, as these command line arguments were removed a while ago in 90496b5456b98ad4f9e0121272ccabfbbef11a91. Otherwise, only minor changes have been made. --- doc/11-cli-commands.md | 117 +++++++++++++++++++---------------------- 1 file changed, 54 insertions(+), 63 deletions(-) diff --git a/doc/11-cli-commands.md b/doc/11-cli-commands.md index 2fdcfc447..914094701 100644 --- a/doc/11-cli-commands.md +++ b/doc/11-cli-commands.md @@ -13,18 +13,18 @@ options. ``` # icinga2 -icinga2 - The Icinga 2 network monitoring daemon (version: v2.11.0) +icinga2 - The Icinga 2 network monitoring daemon (version: v2.14.4) Usage: icinga2 [] Supported commands: * api setup (setup for API) - * ca list (lists all certificate signing requests) - * ca restore (restores a removed certificate request) + * ca list (lists pending certificate signing requests) * ca remove (removes an outstanding certificate request) + * ca restore (restores a removed certificate request) * ca sign (signs an outstanding certificate request) - * console (Icinga debug console) + * console (Icinga console) * daemon (starts Icinga 2) * feature disable (disables specified feature) * feature enable (enables specified feature) @@ -48,8 +48,6 @@ Global options: --color use VT100 color codes even when stdout is not a terminal -D [ --define ] arg define a constant - -a [ --app ] arg application library name (default: icinga) - -l [ --library ] arg load a library -I [ --include ] arg add include search directory -x [ --log-level ] arg specify the log level for the console log. The valid value is either debug, notice, @@ -57,6 +55,8 @@ Global options: -X [ --script-debugger ] whether to enable the script debugger Report bugs at +Get support: +Documentation: Icinga home page: ``` @@ -102,18 +102,6 @@ source /etc/bash-completion.d/icinga2 ## Icinga 2 CLI Global Options -### Application Type - -By default the `icinga2` binary loads the `icinga` library. A different application type -can be specified with the `--app` command-line option. -Note: This is not needed by the average Icinga user, only developers. - -### Libraries - -Instead of loading libraries using the [`library` config directive](17-language-reference.md#library) -you can also use the `--library` command-line option. -Note: This is not needed by the average Icinga user, only developers. - ### Constants [Global constants](17-language-reference.md#constants) can be set using the `--define` command-line option. @@ -144,7 +132,7 @@ Provides helper functions to enable and setup the ``` # icinga2 api setup --help -icinga2 - The Icinga 2 network monitoring daemon (version: v2.11.0) +icinga2 - The Icinga 2 network monitoring daemon (version: v2.14.4) Usage: icinga2 api setup [] @@ -176,20 +164,20 @@ Icinga home page: List and manage incoming certificate signing requests. More details can be found in the [signing methods](06-distributed-monitoring.md#distributed-monitoring-setup-sign-certificates-master) -chapter. This CLI command is available since v2.8. +chapter. ``` # icinga2 ca --help -icinga2 - The Icinga 2 network monitoring daemon (version: v2.11.0) +icinga2 - The Icinga 2 network monitoring daemon (version: v2.14.4) Usage: icinga2 [] Supported commands: - * ca list (lists all certificate signing requests) - * ca sign (signs an outstanding certificate request) - * ca restore (restores a removed certificate request) + * ca list (lists pending certificate signing requests) * ca remove (removes an outstanding certificate request) + * ca restore (restores a removed certificate request) + * ca sign (signs an outstanding certificate request) Global options: -h [ --help ] show this help message @@ -197,8 +185,6 @@ Global options: --color use VT100 color codes even when stdout is not a terminal -D [ --define ] arg define a constant - -a [ --app ] arg application library name (default: icinga) - -l [ --library ] arg load a library -I [ --include ] arg add include search directory -x [ --log-level ] arg specify the log level for the console log. The valid value is either debug, notice, @@ -206,6 +192,8 @@ Global options: -X [ --script-debugger ] whether to enable the script debugger Report bugs at +Get support: +Documentation: Icinga home page: ``` @@ -213,8 +201,8 @@ Icinga home page: ### CLI command: Ca List ``` -icinga2 ca list --help -icinga2 - The Icinga 2 network monitoring daemon (version: v2.11.0) +# icinga2 ca list --help +icinga2 - The Icinga 2 network monitoring daemon (version: v2.14.4) Usage: icinga2 ca list [] @@ -249,11 +237,14 @@ Icinga home page: ## CLI command: Console The CLI command `console` can be used to debug and evaluate Icinga 2 config expressions, -e.g. to test [functions](17-language-reference.md#functions) in your local sandbox. +e.g., to test [functions](17-language-reference.md#functions) in your local sandbox. + +This command can be executed by any user and does not require access to the Icinga 2 configuration. ``` -$ icinga2 console -Icinga 2 (version: v2.11.0) +# icinga2 console +Icinga 2 (version: v2.14.4) +Type $help to view available commands. <1> => function test(name) { <1> .. log("Hello " + name) <1> .. } @@ -268,7 +259,7 @@ Further usage examples can be found in the [library reference](18-library-refere ``` # icinga2 console --help -icinga2 - The Icinga 2 network monitoring daemon (version: v2.11.0) +icinga2 - The Icinga 2 network monitoring daemon (version: v2.14.4) Usage: icinga2 console [] @@ -281,8 +272,6 @@ Global options: --color use VT100 color codes even when stdout is not a terminal -D [ --define ] arg define a constant - -a [ --app ] arg application library name (default: icinga) - -l [ --library ] arg load a library -I [ --include ] arg add include search directory -x [ --log-level ] arg specify the log level for the console log. The valid value is either debug, notice, @@ -297,11 +286,13 @@ Command options: --sandbox enable sandbox mode Report bugs at +Get support: +Documentation: Icinga home page: ``` -On operating systems without the `libedit` library installed there is no +On operating systems without the `libedit` library installed, there is no support for line-editing or a command history. However you can use the `rlwrap` program if you require those features: @@ -311,7 +302,7 @@ rlwrap icinga2 console The debug console can be used to connect to a running Icinga 2 instance using the [REST API](12-icinga2-api.md#icinga2-api). [API permissions](12-icinga2-api.md#icinga2-api-permissions) -are required for executing config expressions and auto-completion. +for `console` are required for executing config expressions and auto-completion. > **Note** > @@ -323,20 +314,20 @@ are required for executing config expressions and auto-completion. You can specify the API URL using the `--connect` parameter. -Although the password can be specified there process arguments on UNIX platforms are -usually visible to other users (e.g. through `ps`). In order to securely specify the -user credentials the debug console supports two environment variables: +Although the password can be specified there, process arguments are usually +visible to other users (e.g. through `ps`). In order to securely specify the +user credentials, the debug console supports two environment variables: Environment variable | Description ---------------------|------------- ICINGA2_API_USERNAME | The API username. ICINGA2_API_PASSWORD | The API password. -Here's an example: +Here is an example: ``` $ ICINGA2_API_PASSWORD=icinga icinga2 console --connect 'https://root@localhost:5665/' -Icinga 2 (version: v2.11.0) +Icinga 2 (version: v2.14.4) <1> => ``` @@ -383,7 +374,7 @@ The `--syntax-only` option can be used in combination with `--eval` or `--file` to check a script for syntax errors. In this mode the script is parsed to identify syntax errors but not evaluated. -Here's an example that retrieves the command that was used by Icinga to check the `icinga2-agent1.localdomain` host: +Here is an example that retrieves the command that was used by Icinga to check the `icinga2-agent1.localdomain` host: ``` $ ICINGA2_API_PASSWORD=icinga icinga2 console --connect 'https://root@localhost:5665/' --eval 'get_host("icinga2-agent1.localdomain").last_check_result.command' | python -m json.tool @@ -405,7 +396,7 @@ Furthermore it allows to run the [configuration validation](11-cli-commands.md#c ``` # icinga2 daemon --help -icinga2 - The Icinga 2 network monitoring daemon (version: v2.11.0) +icinga2 - The Icinga 2 network monitoring daemon (version: v2.14.4) Usage: icinga2 daemon [] @@ -418,8 +409,6 @@ Global options: --color use VT100 color codes even when stdout is not a terminal -D [ --define ] arg define a constant - -a [ --app ] arg application library name (default: icinga) - -l [ --library ] arg load a library -I [ --include ] arg add include search directory -x [ --log-level ] arg specify the log level for the console log. The valid value is either debug, notice, @@ -430,7 +419,8 @@ Command options: -c [ --config ] arg parse a configuration file -z [ --no-config ] start without a configuration file -C [ --validate ] exit after validating the configuration - --dump-objects write icinga2.debug cache file for icinga2 object list + --dump-objects write icinga2.debug cache file for icinga2 object + list -e [ --errorlog ] arg log fatal errors to the specified log file (only works in combination with --daemonize or --close-stdio) @@ -438,6 +428,8 @@ Command options: --close-stdio do not log to stdout (or stderr) after startup Report bugs at +Get support: +Documentation: Icinga home page: ``` @@ -476,8 +468,8 @@ The `feature list` command shows which features are currently enabled: ``` # icinga2 feature list -Disabled features: compatlog debuglog gelf ido-pgsql influxdb livestatus opentsdb perfdata statusdata syslog -Enabled features: api checker command graphite ido-mysql mainlog notification +Disabled features: debuglog elasticsearch gelf ido-mysql ido-pgsql influxdb influxdb2 journald opentsdb perfdata syslog +Enabled features: api checker graphite icingadb mainlog notification ``` ## CLI command: Node @@ -529,7 +521,7 @@ More information can be found in the [troubleshooting](15-troubleshooting.md#tro ``` # icinga2 object --help -icinga2 - The Icinga 2 network monitoring daemon (version: v2.11.0) +icinga2 - The Icinga 2 network monitoring daemon (version: v2.14.4) Usage: icinga2 [] @@ -543,8 +535,6 @@ Global options: --color use VT100 color codes even when stdout is not a terminal -D [ --define ] arg define a constant - -a [ --app ] arg application library name (default: icinga) - -l [ --library ] arg load a library -I [ --include ] arg add include search directory -x [ --log-level ] arg specify the log level for the console log. The valid value is either debug, notice, @@ -552,6 +542,8 @@ Global options: -X [ --script-debugger ] whether to enable the script debugger Report bugs at +Get support: +Documentation: Icinga home page: ``` @@ -571,7 +563,7 @@ You will need them in the [distributed monitoring chapter](06-distributed-monito ``` # icinga2 pki --help -icinga2 - The Icinga 2 network monitoring daemon (version: v2.12.0) +icinga2 - The Icinga 2 network monitoring daemon (version: v2.14.4) Usage: icinga2 [] @@ -591,8 +583,6 @@ Global options: --color use VT100 color codes even when stdout is not a terminal -D [ --define ] arg define a constant - -a [ --app ] arg application library name (default: icinga) - -l [ --library ] arg load a library -I [ --include ] arg add include search directory -x [ --log-level ] arg specify the log level for the console log. The valid value is either debug, notice, @@ -600,6 +590,8 @@ Global options: -X [ --script-debugger ] whether to enable the script debugger Report bugs at +Get support: +Documentation: Icinga home page: ``` @@ -609,7 +601,7 @@ Lists all configured variables (constants) in a similar fashion like [object lis ``` # icinga2 variable --help -icinga2 - The Icinga 2 network monitoring daemon (version: v2.11.0) +icinga2 - The Icinga 2 network monitoring daemon (version: v2.14.4) Usage: icinga2 [] @@ -624,8 +616,6 @@ Global options: --color use VT100 color codes even when stdout is not a terminal -D [ --define ] arg define a constant - -a [ --app ] arg application library name (default: icinga) - -l [ --library ] arg load a library -I [ --include ] arg add include search directory -x [ --log-level ] arg specify the log level for the console log. The valid value is either debug, notice, @@ -633,6 +623,8 @@ Global options: -X [ --script-debugger ] whether to enable the script debugger Report bugs at +Get support: +Documentation: Icinga home page: ``` @@ -651,8 +643,8 @@ You can view a list of enabled and disabled features: ``` # icinga2 feature list -Disabled features: api command compatlog debuglog graphite icingastatus ido-mysql ido-pgsql livestatus notification perfdata statusdata syslog -Enabled features: checker mainlog notification +Disabled features: debuglog elasticsearch gelf ido-mysql ido-pgsql influxdb influxdb2 journald opentsdb perfdata syslog +Enabled features: api checker graphite icingadb mainlog notification ``` Using the `icinga2 feature enable` command you can enable features: @@ -675,10 +667,9 @@ restart Icinga 2. You will need to restart Icinga 2 using the init script after enabling or disabling features. - ## Configuration Validation -Once you've edited the configuration files make sure to tell Icinga 2 to validate +Once you have edited the configuration, make sure to tell Icinga 2 to validate the configuration changes. Icinga 2 will log any configuration error including a hint on the file, the line number and the affected configuration line itself. @@ -716,12 +707,12 @@ to read the [troubleshooting](15-troubleshooting.md#troubleshooting) chapter. You can also use the [CLI command](11-cli-commands.md#cli-command-object) `icinga2 object list` after validation passes to analyze object attributes, inheritance or created objects by apply rules. -Find more on troubleshooting with `object list` in [this chapter](15-troubleshooting.md#troubleshooting-list-configuration-objects). +Find more on troubleshooting with `icinga2 object list` in [this chapter](15-troubleshooting.md#troubleshooting-list-configuration-objects). ## Reload on Configuration Changes -Every time you have changed your configuration you should first tell Icinga 2 +Every time you have changed your configuration, you should first tell Icinga 2 to [validate](11-cli-commands.md#config-validation). If there are no validation errors, you can safely reload the Icinga 2 daemon. From e1a4390b9cfcee4b87cb2b170c9e032ecc0dde22 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 3 Jan 2024 13:18:29 +0100 Subject: [PATCH 158/415] Fix compile error on OpenBSD which has no SSL_OP_NO_RENEGOTIATION --- lib/base/tlsutility.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/base/tlsutility.cpp b/lib/base/tlsutility.cpp index 246bd5aee..fb60e0221 100644 --- a/lib/base/tlsutility.cpp +++ b/lib/base/tlsutility.cpp @@ -93,7 +93,9 @@ static void InitSslContext(const Shared::Ptr& context flags |= SSL_OP_CIPHER_SERVER_PREFERENCE; -#if OPENSSL_VERSION_NUMBER < 0x10100000L +#ifdef LIBRESSL_VERSION_NUMBER + flags |= SSL_OP_NO_CLIENT_RENEGOTIATION; +#elif OPENSSL_VERSION_NUMBER < 0x10100000L SSL_CTX_set_info_callback(sslContext, [](const SSL* ssl, int where, int) { if (where & SSL_CB_HANDSHAKE_DONE) { ssl->s3->flags |= SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS; From 25bbac1677f6497ab35f42bfec7f5bedd99d116e Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 30 Jan 2025 17:24:15 +0100 Subject: [PATCH 159/415] Don't abruptly close anonymous connections This was mistakenly introduced with PR #7686 due to too many open connections (#7680). This was wrong in the sense that closing the connection is simply out of place here and should have been handled differently. After we revised the RPC connection disconnect procedure with `v2.14.4`, it becomes clear why it is wrong, because the connection is closed abruptly before the corresponding response (`result`) has even been written. Now if you remove the disconnect here, shouldn't the issue #7680 occur again, you ask? The answer is no, because we now also have a maximum timeout of `10s` for anonymous connections, after which they are automatically closed. Thanks to the introduction of this timeout by @julianbrost in #8479, this `Disconnect()` call has become superfluous. --- lib/remote/jsonrpcconnection-pki.cpp | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/remote/jsonrpcconnection-pki.cpp b/lib/remote/jsonrpcconnection-pki.cpp index 340e12b30..12b772303 100644 --- a/lib/remote/jsonrpcconnection-pki.cpp +++ b/lib/remote/jsonrpcconnection-pki.cpp @@ -286,14 +286,6 @@ delayed_request: Log(LogInformation, "JsonRpcConnection") << "Certificate request for CN '" << cn << "' is pending. Waiting for approval."; - if (origin) { - auto client (origin->FromClient); - - if (client && !client->GetEndpoint()) { - client->Disconnect(); - } - } - return result; } From dda0da6bf862fcebae12932290221966a0145082 Mon Sep 17 00:00:00 2001 From: Angel Roman Date: Fri, 31 Jan 2025 05:50:41 -0500 Subject: [PATCH 160/415] Merge pull request #10317 from legna-namor/patch-1 Remove RHEL 7 from installation instructions --- AUTHORS | 1 + doc/02-installation.md | 28 ---------------------------- 2 files changed, 1 insertion(+), 28 deletions(-) diff --git a/AUTHORS b/AUTHORS index 7de33117e..73ab10637 100644 --- a/AUTHORS +++ b/AUTHORS @@ -21,6 +21,7 @@ Andres Ivanov Andrew Jaffie Andrew Meyer Andy Grunwald +Angel Roman Ant1x <37016240+Ant1x@users.noreply.github.com> Arnd Hannemann Assaf Flatto diff --git a/doc/02-installation.md b/doc/02-installation.md index 51d24076a..bff6529eb 100644 --- a/doc/02-installation.md +++ b/doc/02-installation.md @@ -100,16 +100,8 @@ subscription-manager repos --enable "codeready-builder-for-rhel-${OSVER}-${ARCH} dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-${OSVER}.noarch.rpm ``` -#### RHEL 7 - -```bash -subscription-manager repos --enable rhel-7-server-optional-rpms - -yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm -``` - ### Fedora Repository @@ -214,14 +206,6 @@ dnf install icinga2 systemctl enable icinga2 systemctl start icinga2 ``` - -#### RHEL 7 - -```bash -yum install icinga2 -systemctl enable icinga2 -systemctl start icinga2 -``` @@ -314,12 +298,6 @@ The packages for RHEL depend on other packages which are distributed as part of ```bash dnf install nagios-plugins-all ``` - -#### RHEL 7 - -```bash -yum install nagios-plugins-all -``` @@ -457,12 +435,6 @@ apt install icingadb-redis ```bash dnf install icingadb-redis ``` - -##### RHEL 7 - -```bash -yum install icingadb-redis -``` From f1f10fdd9ecd48d449080da71b71cc1e5eda42ab Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Wed, 5 Feb 2025 11:05:18 +0100 Subject: [PATCH 161/415] tests: fix FormatDateTime with 32-bit time_t With a 32-bit time_t, two checks in the FormatDateTime test case didn't work properly so far: 1. Every time_t value can be represented by struct tm, hence the test makes no sense on such platforms and is now disabled there similar to how it's already done with other checks in the same function. 2. std::nextafter(2147483647, +double_limit::infinity())) results in something like 2147483647.000000238 which simply results in the limit when cast back to an integer type, so it didn't actually test the overflow. This is fixed by an additional std::ceil()/std::floor(). --- test/base-utility.cpp | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/test/base-utility.cpp b/test/base-utility.cpp index 51eba962b..2273e1b8c 100644 --- a/test/base-utility.cpp +++ b/test/base-utility.cpp @@ -183,8 +183,12 @@ BOOST_AUTO_TEST_CASE(FormatDateTime) { // // These are expected to result in an error due to the intermediate struct tm not being able to represent these // timestamps, so localtime_r() returns EOVERFLOW which makes the implementation throw an exception. - BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", std::nextafter(time_t_limit::min(), 0)), posix_error); - BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", std::nextafter(time_t_limit::max(), 0)), posix_error); + if constexpr (sizeof(time_t) > sizeof(int32_t)) { + BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", std::nextafter(time_t_limit::min(), 0)), posix_error); + BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", std::nextafter(time_t_limit::max(), 0)), posix_error); + } else { + BOOST_WARN_MESSAGE(false, "skipping test for struct tm overflow due to 32 bit time_t"); + } // Excessive format strings can result in something too large for the buffer, errors out to the empty string. // Note: both returning the proper result or throwing an exception would be fine too, unfortunately, that's @@ -214,8 +218,16 @@ BOOST_AUTO_TEST_CASE(FormatDateTime) { } // Out of range timestamps. - BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", std::nextafter(time_t_limit::min(), -double_limit::infinity())), negative_overflow); - BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", std::nextafter(time_t_limit::max(), +double_limit::infinity())), positive_overflow); + // + // At the limits of a 64 bit time_t, doubles can no longer represent each integer value, so a simple x+1 or x-1 can + // have x as the result, hence std::nextafter() is used to get the next representable value. However, around the + // limits of a 32 bit time_t, doubles still can represent decimal places and less than 1 is added or subtracted by + // std::nextafter() and casting back to time_t simply results in the limit again, so std::ceil()/std::floor() is + // used to round it to the next integer value that is actually out of range. + double negative_out_of_range = std::floor(std::nextafter(time_t_limit::min(), -double_limit::infinity())); + double positive_out_of_range = std::ceil(std::nextafter(time_t_limit::max(), +double_limit::infinity())); + BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", negative_out_of_range), negative_overflow); + BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", positive_out_of_range), positive_overflow); } BOOST_AUTO_TEST_SUITE_END() From 988ba18be03dc5ed1834c7a2add4548480ad99a2 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Thu, 6 Feb 2025 16:49:42 +0100 Subject: [PATCH 162/415] ITL docs: list curl_extra_opts as last variable Other plugins list --extra-opts last as it's often some kind of feature of last resort as it provides an option that can't be set in another way. For consistency, this also moves it to the end for the curl check command. --- doc/10-icinga-template-library.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md index 485e6b7e0..736a8acba 100644 --- a/doc/10-icinga-template-library.md +++ b/doc/10-icinga-template-library.md @@ -767,7 +767,6 @@ Custom variables passed as [command parameters](03-monitoring-basics.md#command- Name | Description ---------------------------------|--------------------------------- -curl_extra_opts | **Optional.** Read options from an ini file. curl_vhost | **Optional.** The virtual host that should be sent in the "Host" header. curl_ip | **Optional.** The host's address. Defaults to "$address$" if the host's `address` attribute is set, "$address6$" otherwise. curl_port | **Optional.** The TCP port. Defaults to 80 when not using SSL, 443 otherwise. @@ -814,6 +813,7 @@ curl_cookie_jar_file | **Optional.** Path to a cookie jar file. Stor curl_warning | **Optional.** The warning threshold. curl_critical | **Optional.** The critical threshold. curl_timeout | **Optional.** Seconds before connection times out. +curl_extra_opts | **Optional.** Read options from an ini file. ### icmp From 1df7f3f7c7592c12f88e66fde5bee46855573d71 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Thu, 6 Feb 2025 17:08:44 +0100 Subject: [PATCH 163/415] ITL docs: add missing nscp_extra_opts --- doc/10-icinga-template-library.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md index 736a8acba..b719f9916 100644 --- a/doc/10-icinga-template-library.md +++ b/doc/10-icinga-template-library.md @@ -1066,6 +1066,7 @@ nscp_warn | **Optional.** The warning threshold. nscp_crit | **Optional.** The critical threshold. nscp_timeout | **Optional.** The query timeout in seconds. nscp_showall | **Optional.** Use with SERVICESTATE to see working services or PROCSTATE for running processes. Defaults to false. +nscp_extra_opts | **Optional.** Read extra plugin options from an ini file. ### ntp_time From 3fcc909cdc5ace7542a026c251dce7e65ebafd09 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 3 Mar 2025 16:52:17 +0100 Subject: [PATCH 164/415] Don't mix C and C++ compiler flags --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f6b297b5a..6041840d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -239,7 +239,7 @@ set(CMAKE_MACOSX_RPATH 1) set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH};${CMAKE_INSTALL_FULL_LIBDIR}/icinga2") if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -Winconsistent-missing-override") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Winconsistent-missing-override") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Qunused-arguments -fcolor-diagnostics -fno-limit-debug-info") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Qunused-arguments -fcolor-diagnostics -fno-limit-debug-info") @@ -258,7 +258,7 @@ if(CMAKE_C_COMPILER_ID STREQUAL "SunPro") endif() if(CMAKE_C_COMPILER_ID STREQUAL "GNU") - set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -Wsuggest-override") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-override") if(CMAKE_SYSTEM_NAME MATCHES AIX) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -lpthread") From 6ca0611f3d448a6473058f8d0d44119882286560 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 4 Mar 2025 17:34:38 +0100 Subject: [PATCH 165/415] IcingaDB: Don't publish useless data to Redis The Icinga DB daemon processes the data from the `IcingaApplication` type only and Icinga DB Web also uses only those stats. However, before this commit, Icinga DB published all kinds of useless stats to Redis each second, like the number of (un)reachable hosts, services, and so on, which is waste of CPU and some other resources. This commit reduces the published data drastically to only those simple stats coming from the `IcingaApplication` type. --- lib/icingadb/icingadb-stats.cpp | 37 +++++---------------------------- lib/icingadb/icingadb.hpp | 2 +- 2 files changed, 6 insertions(+), 33 deletions(-) diff --git a/lib/icingadb/icingadb-stats.cpp b/lib/icingadb/icingadb-stats.cpp index d05dfe55f..c43ae4ceb 100644 --- a/lib/icingadb/icingadb-stats.cpp +++ b/lib/icingadb/icingadb-stats.cpp @@ -12,42 +12,15 @@ using namespace icinga; Dictionary::Ptr IcingaDB::GetStats() { - Dictionary::Ptr stats = new Dictionary(); - - //TODO: Figure out if more stats can be useful here. - Namespace::Ptr statsFunctions = ScriptGlobal::Get("StatsFunctions", &Empty); - - if (!statsFunctions) - Dictionary::Ptr(); - - ObjectLock olock(statsFunctions); - - for (auto& kv : statsFunctions) - { - Function::Ptr func = kv.second.Val; - - if (!func) - BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid status function name.")); - - Dictionary::Ptr status = new Dictionary(); - Array::Ptr perfdata = new Array(); - func->Invoke({ status, perfdata }); - - stats->Set(kv.first, new Dictionary({ - { "status", status }, - { "perfdata", Serialize(perfdata, FAState) } - })); - } - - typedef Dictionary::Ptr DP; - DP app = DP(DP(DP(stats->Get("IcingaApplication"))->Get("status"))->Get("icingaapplication"))->Get("app"); + Dictionary::Ptr status = new Dictionary(); + IcingaApplication::StatsFunc(status, nullptr); + Dictionary::Ptr app(Dictionary::Ptr(status->Get("icingaapplication"))->Get("app")); app->Set("program_start", TimestampToMilliseconds(Application::GetStartTime())); - auto localEndpoint (Endpoint::GetLocalEndpoint()); - if (localEndpoint) { + if (auto localEndpoint(Endpoint::GetLocalEndpoint()); localEndpoint) { app->Set("endpoint_id", GetObjectIdentifier(localEndpoint)); } - return stats; + return new Dictionary{{ "IcingaApplication", new Dictionary{{"status", status}}}}; } diff --git a/lib/icingadb/icingadb.hpp b/lib/icingadb/icingadb.hpp index 6652d9c1f..b943d7f4b 100644 --- a/lib/icingadb/icingadb.hpp +++ b/lib/icingadb/icingadb.hpp @@ -143,7 +143,7 @@ private: Dictionary::Ptr SerializeState(const Checkable::Ptr& checkable); /* Stats */ - Dictionary::Ptr GetStats(); + static Dictionary::Ptr GetStats(); /* utilities */ static String FormatCheckSumBinary(const String& str); From 6a888e14944fbf8369a19354605bd54f7f0e518f Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 27 Feb 2025 17:20:07 +0100 Subject: [PATCH 166/415] String: Mark move constructor & assignment op as `noexcept` The Icinga DB code performs intensive operations on certain STL containers, primarily on `std::vector`. Specifically, it inserts 2-3 new elements at the beginning of a vector containing thousands of elements. Without this commit, all the existing elements would be unnecessarily copied just to accommodate the new elements at the front. By making this change, the compiler is able to optimize STL operations like `push_back`, `emplace_back`, and `insert`, enabling it to prefer the move constructor over copy operations, provided it is guaranteed that no exceptions will be thrown. --- lib/base/string.cpp | 4 ++-- lib/base/string.hpp | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/base/string.cpp b/lib/base/string.cpp index 3c440cd77..bad3116a5 100644 --- a/lib/base/string.cpp +++ b/lib/base/string.cpp @@ -33,7 +33,7 @@ String::String(const String& other) : m_Data(other) { } -String::String(String&& other) +String::String(String&& other) noexcept : m_Data(std::move(other.m_Data)) { } @@ -66,7 +66,7 @@ String& String::operator=(const String& rhs) return *this; } -String& String::operator=(String&& rhs) +String& String::operator=(String&& rhs) noexcept { m_Data = std::move(rhs.m_Data); return *this; diff --git a/lib/base/string.hpp b/lib/base/string.hpp index 0eb08b527..896c74d0b 100644 --- a/lib/base/string.hpp +++ b/lib/base/string.hpp @@ -44,7 +44,7 @@ public: String(std::string data); String(String::SizeType n, char c); String(const String& other); - String(String&& other); + String(String&& other) noexcept; #ifndef _MSC_VER String(Value&& other); @@ -56,7 +56,7 @@ public: { } String& operator=(const String& rhs); - String& operator=(String&& rhs); + String& operator=(String&& rhs) noexcept; String& operator=(Value&& rhs); String& operator=(const std::string& rhs); String& operator=(const char *rhs); From e308552eccbab74069c30bc2234cd26e5e5e1f9f Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Thu, 6 Mar 2025 11:46:48 +0100 Subject: [PATCH 167/415] Add test that std::vector uses move overloads --- test/CMakeLists.txt | 1 + test/base-string.cpp | 25 +++++++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index a255178da..6ceb48683 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -165,6 +165,7 @@ add_boost_test(base base_string/replace base_string/index base_string/find + base_string/vector_move base_timer/construct base_timer/interval base_timer/invoke diff --git a/test/base-string.cpp b/test/base-string.cpp index 835b1a643..5b28c5481 100644 --- a/test/base-string.cpp +++ b/test/base-string.cpp @@ -1,6 +1,7 @@ /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ #include "base/string.hpp" +#include #include using namespace icinga; @@ -101,4 +102,28 @@ BOOST_AUTO_TEST_CASE(find) BOOST_CHECK(s.FindFirstOf("xl") == 2); } +// Check that if a std::vector is grown beyond its capacity (i.e. it has to reallocate the memory), +// it uses the move constructor of icinga::String (i.e. the underlying string storage stays the same). +BOOST_AUTO_TEST_CASE(vector_move) +{ + std::vector vec { + // std::string (which is internally used by icinga::String) has an optimization that small strings can be + // allocated inside it instead of in a separate heap allocation. In that case, the small string would still be + // copied even by the move constructor. Using sizeof() ensures that the string is long enough so that it must + // be allocated separately and can be used to test for the desired move to happen. + std::string(sizeof(String) + 1, 'A'), + }; + + void *oldAddr = vec[0].GetData().data(); + // Sanity check that the data buffer is actually allocated outside the icinga::String instance. + BOOST_CHECK(!(&vec[0] <= oldAddr && oldAddr < &vec[1])); + + // Force the vector to grow. + vec.reserve(vec.capacity() + 1); + + // If the string was moved, the location of its underlying data buffer should not have changed. + void *newAddr = vec[0].GetData().data(); + BOOST_CHECK_EQUAL(oldAddr, newAddr); +} + BOOST_AUTO_TEST_SUITE_END() From 3e9292a349daf9432430487c57bbce73869b1b22 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 27 Feb 2025 17:38:05 +0100 Subject: [PATCH 168/415] Value: Add a specialized rvalue reference of `Get()` The move `String(Value&&)` constructor tries to partially move `String` values from a `Value` type. However, since there was no an appropriate `Value::Get()` implementation that binds to the requested move operation, the compiler will actually not move the value but copy it instead as the only available implementation of `Value::Get()` returns a const reference `const T&`. This commit adds a new overload that returns a non-const reference and allows to optionally move the string value of a Value type. --- lib/base/string.cpp | 2 +- lib/base/value.cpp | 4 ++++ lib/base/value.hpp | 10 ++++++++++ test/CMakeLists.txt | 1 + test/base-string.cpp | 17 +++++++++++++++++ 5 files changed, 33 insertions(+), 1 deletion(-) diff --git a/lib/base/string.cpp b/lib/base/string.cpp index bad3116a5..2806902ac 100644 --- a/lib/base/string.cpp +++ b/lib/base/string.cpp @@ -47,7 +47,7 @@ String::String(Value&& other) String& String::operator=(Value&& other) { if (other.IsString()) - m_Data = std::move(other.Get()); + *this = std::move(other.Get()); // Will atomically bind to the move assignment operator below. else *this = static_cast(other); diff --git a/lib/base/value.cpp b/lib/base/value.cpp index ebf3ba60b..1e357a92a 100644 --- a/lib/base/value.cpp +++ b/lib/base/value.cpp @@ -9,9 +9,13 @@ using namespace icinga; template class boost::variant; template const double& Value::Get() const; +template double& Value::Get(); template const bool& Value::Get() const; +template bool& Value::Get(); template const String& Value::Get() const; +template String& Value::Get(); template const Object::Ptr& Value::Get() const; +template Object::Ptr& Value::Get(); const Value icinga::Empty; diff --git a/lib/base/value.hpp b/lib/base/value.hpp index 6e64abb43..9533d710c 100644 --- a/lib/base/value.hpp +++ b/lib/base/value.hpp @@ -140,14 +140,24 @@ public: return boost::get(m_Value); } + template + T& Get() + { + return boost::get(m_Value); + } + private: boost::variant m_Value; }; extern template const double& Value::Get() const; +extern template double& Value::Get(); extern template const bool& Value::Get() const; +extern template bool& Value::Get(); extern template const String& Value::Get() const; +extern template String& Value::Get(); extern template const Object::Ptr& Value::Get() const; +extern template Object::Ptr& Value::Get(); extern const Value Empty; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 6ceb48683..c4b1041dd 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -166,6 +166,7 @@ add_boost_test(base base_string/index base_string/find base_string/vector_move + base_string/move_string_out_of_Value_type base_timer/construct base_timer/interval base_timer/invoke diff --git a/test/base-string.cpp b/test/base-string.cpp index 5b28c5481..50c1f6af8 100644 --- a/test/base-string.cpp +++ b/test/base-string.cpp @@ -1,6 +1,7 @@ /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ #include "base/string.hpp" +#include "base/value.hpp" #include #include @@ -126,4 +127,20 @@ BOOST_AUTO_TEST_CASE(vector_move) BOOST_CHECK_EQUAL(oldAddr, newAddr); } +// Test that the move constructor of icinga::String actually moves the underlying std::string out of a Value instance. +// The constructor overload is only available on non-Windows platforms though, so we need to skip the test on Windows. +BOOST_AUTO_TEST_CASE(move_string_out_of_Value_type) +{ +#ifndef _MSC_VER + Value value("Icinga 2"); + String other = value.Get(); // We didn't request a move, so this should just copy the string. + BOOST_CHECK_EQUAL("Icinga 2", value.Get()); + BOOST_CHECK_EQUAL("Icinga 2", other); + + String newStr = std::move(value); + BOOST_CHECK_EQUAL("", value.Get()); + BOOST_CHECK_EQUAL(newStr, "Icinga 2"); +#endif /* _MSC_VER */ +} + BOOST_AUTO_TEST_SUITE_END() From 784867b3f7c9736e4b49b78c51de2c59df4d7fa4 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Mon, 10 Mar 2025 09:28:33 +0100 Subject: [PATCH 169/415] Avoid undefined behavior in string/vector_move test vec[1] is equivalent to vec[vec.size()] at that point and thus not a valid element of the vector, making the use of operator[] undefined behavior here. With some compiler flags (like those used in package builds on RHEL and similar), the compiler (rightfully) aborts the program on this out of bounds access: 68/178 Test #68: base-base_string/vector_move ............................................***Failed 0.01 sec /usr/include/c++/14/bits/stl_vector.h:1130: std::vector<_Tp, _Alloc>::reference std::vector<_Tp, _Alloc>::operator[](size_type) [with _Tp = icinga::String; _Alloc = std::allocator; reference = icinga::String&; size_type = long unsigned int]: Assertion '__n < this->size()' failed. Running 1 test case... unknown location(0): fatal error: in "base_string/vector_move": signal: SIGABRT (application abort requested) /builds/packages/icinga2/packaging/fedora/41/BUILD/icinga2-2.14.5+467.g206d7cda1-build/icinga2-2.14.5+467.g206d7cda1/test/base-string.cpp(120): last checkpoint *** 1 failure is detected in the test module "icinga2" This commit fixes this by taking the indirection through .data() and using plain pointer arithmetic instead. --- test/base-string.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/base-string.cpp b/test/base-string.cpp index 50c1f6af8..7a6b36264 100644 --- a/test/base-string.cpp +++ b/test/base-string.cpp @@ -117,7 +117,7 @@ BOOST_AUTO_TEST_CASE(vector_move) void *oldAddr = vec[0].GetData().data(); // Sanity check that the data buffer is actually allocated outside the icinga::String instance. - BOOST_CHECK(!(&vec[0] <= oldAddr && oldAddr < &vec[1])); + BOOST_CHECK(!(vec.data() <= oldAddr && oldAddr < vec.data() + vec.size())); // Force the vector to grow. vec.reserve(vec.capacity() + 1); From 7962121faa108c95593e3a50a9bff9bbb963b067 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 11 Mar 2025 16:11:05 +0100 Subject: [PATCH 170/415] GitHub actions: also test the still packaged 32-bit Debian --- .github/workflows/linux.yml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 1b1b1ac89..829161cce 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -13,7 +13,7 @@ concurrency: jobs: linux: - name: ${{ matrix.distro }} + name: ${{ matrix.distro }}${{ matrix.platform != 'linux/amd64' && format(' ({0})', matrix.platform) || '' }} runs-on: ubuntu-latest strategy: @@ -49,6 +49,15 @@ jobs: - ubuntu:24.04 - ubuntu:24.10 + platform: + - linux/amd64 + + include: + - distro: debian:11 + platform: linux/386 + - distro: debian:12 + platform: linux/386 + steps: - name: Checkout HEAD uses: actions/checkout@v3 @@ -62,4 +71,4 @@ jobs: - name: Build run: >- docker run --rm -v "$(pwd):/icinga2" -e DISTRO=${{ matrix.distro }} - ${{ matrix.distro }} /icinga2/.github/workflows/linux.bash + --platform ${{ matrix.platform }} ${{ matrix.distro }} /icinga2/.github/workflows/linux.bash From cc5f01d47f3ebd2f89f5a9e683d09fa96fd6146a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Wed, 12 Mar 2025 09:57:16 +0100 Subject: [PATCH 171/415] GitHub actions: run ninja with -v to show all compiler flags. --- .github/workflows/linux.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linux.bash b/.github/workflows/linux.bash index f493633ef..a94f7d1b7 100755 --- a/.github/workflows/linux.bash +++ b/.github/workflows/linux.bash @@ -78,7 +78,7 @@ cmake \ -DICINGA2_GROUP=$(id -gn) \ $CMAKE_OPTS .. -ninja +ninja -v ninja test ninja install From 4b18f62a11cbefb6e25e2fd625ad18e9a899edf1 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Thu, 27 Feb 2025 17:23:09 +0100 Subject: [PATCH 172/415] Add ConfigType::BeforeOnAllConfigLoaded signal Allows to hook into the config loading process just before OnAllConfigLoaded() is called on a bunch of individual config objects. Allows doing some operations more efficiently at once for all objects. Intended use: when adding a number of dependencies, it has to be checked whether this uses any cycles. This can be done more efficiently if all dependencies are checked at once. So far, this is with a case-distinction for initially loaded files in DaemonUtility::LoadConfigFiles() and for dependencies created by runtime updates in Dependency::OnAllConfigLoaded(). The mechanism added by this commit allows to unify the handling of both cases (done in a following commit). --- lib/base/configtype.hpp | 9 +++++++++ lib/config/configitem.cpp | 24 +++++++++++++++++++++--- lib/config/configitem.hpp | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/lib/base/configtype.hpp b/lib/base/configtype.hpp index c77fc5ec2..f709b2c0d 100644 --- a/lib/base/configtype.hpp +++ b/lib/base/configtype.hpp @@ -9,11 +9,13 @@ #include "base/dictionary.hpp" #include #include +#include namespace icinga { class ConfigObject; +class ConfigItems; class ConfigType { @@ -48,6 +50,13 @@ for (const auto& object : objects) { int GetObjectCount() const; + /** + * Signal that allows hooking into the config loading process just before ConfigObject::OnAllConfigLoaded() is + * called for a bunch of objects. A vector of pointers to these objects is passed as an argument. All elements + * are of the object type the signal is called on. + */ + boost::signals2::signal BeforeOnAllConfigLoaded; + private: typedef std::unordered_map > ObjectMap; typedef std::vector > ObjectVector; diff --git a/lib/config/configitem.cpp b/lib/config/configitem.cpp index e8a509275..5dce19eda 100644 --- a/lib/config/configitem.cpp +++ b/lib/config/configitem.cpp @@ -499,6 +499,23 @@ bool ConfigItem::CommitNewItems(const ActivationContext::Ptr& context, WorkQueue auto items (itemsByType.find(type.get())); if (items != itemsByType.end()) { + auto configType = dynamic_cast(type.get()); + + // Skip the call if no handlers are connected (signal::empty()) or there are no items (vector::empty()). + if (configType && !configType->BeforeOnAllConfigLoaded.empty() && !items->second.empty()) { + // Call the signal in the WorkQueue so that if an exception is thrown, it is caught by the WorkQueue + // and then reported like any other config validation error. + upq.Enqueue([&configType, &items]() { + configType->BeforeOnAllConfigLoaded(ConfigItems(items->second)); + }); + + upq.Join(); + + if (upq.HasExceptions()) { + return false; + } + } + upq.ParallelFor(items->second, [¬ified_items](const ItemPair& ip) { const ConfigItem::Ptr& item = ip.first; @@ -525,6 +542,10 @@ bool ConfigItem::CommitNewItems(const ActivationContext::Ptr& context, WorkQueue }); upq.Join(); + + if (upq.HasExceptions()) { + return false; + } } } @@ -534,9 +555,6 @@ bool ConfigItem::CommitNewItems(const ActivationContext::Ptr& context, WorkQueue << "Sent OnAllConfigLoaded to " << notified_items << " items of type '" << type->GetName() << "'."; #endif /* I2_DEBUG */ - if (upq.HasExceptions()) - return false; - notified_items = 0; for (auto loadDep : type->GetLoadDependencies()) { auto items (itemsByType.find(loadDep)); diff --git a/lib/config/configitem.hpp b/lib/config/configitem.hpp index b99cd08e5..007a3c08a 100644 --- a/lib/config/configitem.hpp +++ b/lib/config/configitem.hpp @@ -101,6 +101,39 @@ private: static bool CommitNewItems(const ActivationContext::Ptr& context, WorkQueue& upq, std::vector& newItems); }; +/** + * Helper class for exposing config items being committed to the ConfigType::BeforeOnAllConfigLoaded callback. + * + * This class wraps a reference to an internal data structure used in ConfigItem::CommitNewItems() and provides + * functions useful for the callbacks without exposing the internals of CommitNewItems(). + */ +class ConfigItems +{ + explicit ConfigItems(std::vector>& items) : m_Items(items) {} + + std::vector>& m_Items; + + friend ConfigItem; + +public: + /** + * ForEachObject(f) calls f(t) for each object T::Ptr t in vector of underlying config items. + * + * @tparam T ConfigObject type to iterate over + * @tparam F Callback functor type (usually automatically deduced from func) + * @param func Functor accepting T::Ptr as an argument to be called for each object + */ + template + void ForEachObject(F func) const + { + for (const auto& item : m_Items) { + if (typename T::Ptr obj = dynamic_pointer_cast(item.first->GetObject()); obj) { + func(std::move(obj)); + } + } + } +}; + } #endif /* CONFIGITEM_H */ From 500ad70b8cc89ce5c856bad91021ee76a6b84848 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Mon, 10 Mar 2025 11:56:23 +0100 Subject: [PATCH 173/415] Implement std::hash> for old Boost versions Boost only implements it iself starting from version 1.74, but a specialization of std::hash<> can be added trivially to allow the use of std::unordered_set> and std::unordered_map, V>. Being unable to use such types already came up a few types in the past, often resulting in the use of raw pointer instead which always involves an additional "is this safe?"/"could the object go out of scope?" discussion. This commit simply solves this for the future by simply allowing the use of intrusive_ptr in unordered containers. --- lib/base/CMakeLists.txt | 1 + lib/base/intrusive-ptr.hpp | 22 ++++++++++++++++++++++ lib/base/object.hpp | 1 + lib/base/shared.hpp | 1 + 4 files changed, 25 insertions(+) create mode 100644 lib/base/intrusive-ptr.hpp diff --git a/lib/base/CMakeLists.txt b/lib/base/CMakeLists.txt index 6f2a6d95c..59e836443 100644 --- a/lib/base/CMakeLists.txt +++ b/lib/base/CMakeLists.txt @@ -38,6 +38,7 @@ set(base_SOURCES filelogger.cpp filelogger.hpp filelogger-ti.hpp function.cpp function.hpp function-ti.hpp function-script.cpp functionwrapper.hpp initialize.cpp initialize.hpp + intrusive-ptr.hpp io-engine.cpp io-engine.hpp journaldlogger.cpp journaldlogger.hpp journaldlogger-ti.hpp json.cpp json.hpp json-script.cpp diff --git a/lib/base/intrusive-ptr.hpp b/lib/base/intrusive-ptr.hpp new file mode 100644 index 000000000..eb0f67e66 --- /dev/null +++ b/lib/base/intrusive-ptr.hpp @@ -0,0 +1,22 @@ +/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */ + +#pragma once + +#include "base/i2-base.hpp" +#include +#include +#include + +// std::hash is only implemented starting from Boost 1.74. Implement it ourselves for older version to allow using +// boost::intrusive_ptr inside std::unordered_set<> or as the key of std::unordered_map<>. +// https://github.com/boostorg/smart_ptr/commit/5a18ffdc5609a0e64b63e47cb81c4f0847e0c087 +#if BOOST_VERSION < 107400 +template +struct std::hash> +{ + std::size_t operator()(const boost::intrusive_ptr& ptr) const noexcept + { + return std::hash{}(ptr.get()); + } +}; +#endif /* BOOST_VERSION < 107400 */ diff --git a/lib/base/object.hpp b/lib/base/object.hpp index aae28ae88..008426b8d 100644 --- a/lib/base/object.hpp +++ b/lib/base/object.hpp @@ -5,6 +5,7 @@ #include "base/i2-base.hpp" #include "base/debug.hpp" +#include "base/intrusive-ptr.hpp" #include #include #include diff --git a/lib/base/shared.hpp b/lib/base/shared.hpp index 63b35cb2f..2acec012e 100644 --- a/lib/base/shared.hpp +++ b/lib/base/shared.hpp @@ -4,6 +4,7 @@ #define SHARED_H #include "base/atomic.hpp" +#include "base/intrusive-ptr.hpp" #include #include #include From c1b270f39f4c4f3acf57f5413fc7b2f68cfe729c Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Wed, 5 Mar 2025 11:33:30 +0100 Subject: [PATCH 174/415] Rework dependency cycle check This commit groups a bunch of structs and static functions inside dependency.cpp into a new DependencyCycleChecker helper class. In the process, the implementation was changed a bit, the behavior should be unchanged except for a more user-friendly error message in the exception. --- lib/icinga/dependency.cpp | 144 +++++++++++++++++++++----------------- 1 file changed, 81 insertions(+), 63 deletions(-) diff --git a/lib/icinga/dependency.cpp b/lib/icinga/dependency.cpp index 2843b906c..55e681c88 100644 --- a/lib/icinga/dependency.cpp +++ b/lib/icinga/dependency.cpp @@ -9,7 +9,9 @@ #include "base/exception.hpp" #include #include +#include #include +#include using namespace icinga; @@ -17,95 +19,111 @@ REGISTER_TYPE(Dependency); bool Dependency::m_AssertNoCyclesForIndividualDeps = false; -struct DependencyCycleNode +/** + * Helper class to search for cycles in the dependency graph. + * + * State is stored inside the class and no synchronization is done, + * hence instances of this class must not be used concurrently. + */ +class DependencyCycleChecker { - bool Visited = false; - bool OnStack = false; -}; + struct Node + { + bool Visited = false; + bool OnStack = false; + }; -struct DependencyStackFrame -{ - ConfigObject::Ptr Node; - bool Implicit; + std::unordered_map m_Nodes; - inline DependencyStackFrame(ConfigObject::Ptr node, bool implicit = false) : Node(std::move(node)), Implicit(implicit) - { } -}; + // Stack representing the path currently visited by AssertNoCycle(). Dependency::Ptr represents an edge from its + // child to parent, Service::Ptr represents the implicit dependency of that service to its host. + std::vector> m_Stack; -struct DependencyCycleGraph -{ - std::map Nodes; - std::vector Stack; -}; +public: + /** + * Searches the dependency graph for cycles and throws an exception if one is found. + * + * Only the part of the graph that's reachable from the starting node when traversing dependencies towards the + * parents is searched. In order to check that there are no cycles in the whole dependency graph, this method + * has to be called for every checkable. For this, the method can be called on the same DependencyCycleChecker + * instance multiple times, in which case parts of the graph aren't searched twice. However, if the graph structure + * changes, a new DependencyCycleChecker instance must be used. + * + * @param checkable Starting node for the cycle search. + * @throws ScriptError A dependency cycle was found. + */ + void AssertNoCycle(const Checkable::Ptr& checkable) + { + auto& node = m_Nodes[checkable]; -static void AssertNoDependencyCycle(const Checkable::Ptr& checkable, DependencyCycleGraph& graph, bool implicit = false); + if (node.OnStack) { + std::ostringstream s; + s << "Dependency cycle:"; + for (const auto& obj : m_Stack) { + Checkable::Ptr child, parent; + Dependency::Ptr dependency; -static void AssertNoParentDependencyCycle(const Checkable::Ptr& parent, DependencyCycleGraph& graph, bool implicit) -{ - if (graph.Nodes[parent].OnStack) { - std::ostringstream oss; - oss << "Dependency cycle:\n"; + if (std::holds_alternative(obj)) { + dependency = std::get(obj); + parent = dependency->GetParent(); + child = dependency->GetChild(); + } else { + const auto& service = std::get(obj); + parent = service->GetHost(); + child = service; + } - for (auto& frame : graph.Stack) { - oss << frame.Node->GetReflectionType()->GetName() << " '" << frame.Node->GetName() << "'"; - - if (frame.Implicit) { - oss << " (implicit)"; + auto quoted = [](const String& str) { return std::quoted(str.GetData()); }; + s << "\n\t- " << child->GetReflectionType()->GetName() << " " << quoted(child->GetName()) << " depends on "; + if (child == parent) { + s << "itself"; + } else { + s << parent->GetReflectionType()->GetName() << " " << quoted(parent->GetName()); + } + if (dependency) { + s << " (Dependency " << quoted(dependency->GetShortName()) << " " << dependency->GetDebugInfo() << ")"; + } else { + s << " (implicit)"; + } } - - oss << "\n-> "; + BOOST_THROW_EXCEPTION(ScriptError(s.str())); } - oss << parent->GetReflectionType()->GetName() << " '" << parent->GetName() << "'"; - - if (implicit) { - oss << " (implicit)"; + if (node.Visited) { + return; } - - BOOST_THROW_EXCEPTION(ScriptError(oss.str())); - } - - AssertNoDependencyCycle(parent, graph, implicit); -} - -static void AssertNoDependencyCycle(const Checkable::Ptr& checkable, DependencyCycleGraph& graph, bool implicit) -{ - auto& node (graph.Nodes[checkable]); - - if (!node.Visited) { node.Visited = true; + node.OnStack = true; - graph.Stack.emplace_back(checkable, implicit); - for (auto& dep : checkable->GetDependencies()) { - graph.Stack.emplace_back(dep); - AssertNoParentDependencyCycle(dep->GetParent(), graph, false); - graph.Stack.pop_back(); + // Implicit dependency of each service to its host + if (auto service (dynamic_pointer_cast(checkable)); service) { + m_Stack.emplace_back(service); + AssertNoCycle(service->GetHost()); + m_Stack.pop_back(); } - { - auto service (dynamic_pointer_cast(checkable)); - - if (service) { - AssertNoParentDependencyCycle(service->GetHost(), graph, true); - } + // Explicitly configured dependency objects + for (const auto& dep : checkable->GetDependencies()) { + m_Stack.emplace_back(dep); + AssertNoCycle(dep->GetParent()); + m_Stack.pop_back(); } - graph.Stack.pop_back(); node.OnStack = false; } -} +}; void Dependency::AssertNoCycles() { - DependencyCycleGraph graph; + DependencyCycleChecker checker; for (auto& host : ConfigType::GetObjectsByType()) { - AssertNoDependencyCycle(host, graph); + checker.AssertNoCycle(host); } for (auto& service : ConfigType::GetObjectsByType()) { - AssertNoDependencyCycle(service, graph); + checker.AssertNoCycle(service); } m_AssertNoCyclesForIndividualDeps = true; @@ -192,10 +210,10 @@ void Dependency::OnAllConfigLoaded() m_Parent->AddReverseDependency(this); if (m_AssertNoCyclesForIndividualDeps) { - DependencyCycleGraph graph; + DependencyCycleChecker checker; try { - AssertNoDependencyCycle(m_Parent, graph); + checker.AssertNoCycle(m_Parent); } catch (...) { m_Child->RemoveDependency(this); m_Parent->RemoveReverseDependency(this); From 8e7e687b96f1ef8dc779010ac640455fc7a814f5 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Wed, 5 Mar 2025 12:11:45 +0100 Subject: [PATCH 175/415] Unify depependency cycle check code. This commit removes a distinction in how dependency objects are checked for cycles in the resulting graph depending on whether they are part of the initially loaded configuration during process startup or as part of a runtime update. The DependencyCycleChecker helper class is extended with a mechanism that allows additional dependencies to be considered during the cycle search. This allows using it to check for cycles before actually registering the dependencies with the checkables. The aforementioned case-distinction for initial/runtime-update config is removed by making use of the newly added BeforeOnAllConfigLoaded signal to perform the cycle check at once for each batch of dependencies inside ConfigItem::CommitNewItems() for both cases now. During the initial config loading, there can be multiple batches of dependencies as objects from apply rules are created separately, so parts of the dependency graph might be visited multiple times now, however that is limited to a minimum as only parts of the graph that are reachable from the newly added dependencies are searched. --- lib/cli/daemonutility.cpp | 11 ------ lib/icinga/dependency.cpp | 83 +++++++++++++++++++++++++++------------ lib/icinga/dependency.hpp | 11 ++++-- 3 files changed, 65 insertions(+), 40 deletions(-) diff --git a/lib/cli/daemonutility.cpp b/lib/cli/daemonutility.cpp index 9e910f313..5e250d22c 100644 --- a/lib/cli/daemonutility.cpp +++ b/lib/cli/daemonutility.cpp @@ -256,17 +256,6 @@ bool DaemonUtility::LoadConfigFiles(const std::vector& configs, upq.SetName("DaemonUtility::LoadConfigFiles"); bool result = ConfigItem::CommitItems(ascope.GetContext(), upq, newItems); - if (result) { - try { - Dependency::AssertNoCycles(); - } catch (...) { - Log(LogCritical, "config") - << DiagnosticInformation(boost::current_exception(), false); - - result = false; - } - } - if (!result) { ConfigCompilerContext::GetInstance()->CancelObjectsFile(); return false; diff --git a/lib/icinga/dependency.cpp b/lib/icinga/dependency.cpp index 55e681c88..a9a7bf372 100644 --- a/lib/icinga/dependency.cpp +++ b/lib/icinga/dependency.cpp @@ -17,7 +17,12 @@ using namespace icinga; REGISTER_TYPE(Dependency); -bool Dependency::m_AssertNoCyclesForIndividualDeps = false; +INITIALIZE_ONCE(&Dependency::StaticInitialize); + +void Dependency::StaticInitialize() +{ + ConfigType::Get()->BeforeOnAllConfigLoaded.connect(&BeforeOnAllConfigLoadedHandler); +} /** * Helper class to search for cycles in the dependency graph. @@ -31,6 +36,7 @@ class DependencyCycleChecker { bool Visited = false; bool OnStack = false; + std::vector ExtraDependencies; }; std::unordered_map m_Nodes; @@ -40,6 +46,19 @@ class DependencyCycleChecker std::vector> m_Stack; public: + /** + * Add a dependency to this DependencyCycleChecker that will be considered by AssertNoCycle() in addition to + * dependencies already registered to the checkables. This allows checking if additional dependencies would cause + * a cycle before actually registering them to the checkables. + * + * @param dependency Dependency to additionally consider during the cycle search. + */ + void AddExtraDependency(Dependency::Ptr dependency) + { + auto& node = m_Nodes[dependency->GetChild()]; + node.ExtraDependencies.emplace_back(std::move(dependency)); + } + /** * Searches the dependency graph for cycles and throws an exception if one is found. * @@ -110,23 +129,43 @@ public: m_Stack.pop_back(); } + // Additional dependencies to consider + for (const auto& dep : node.ExtraDependencies) { + m_Stack.emplace_back(dep); + AssertNoCycle(dep->GetParent()); + m_Stack.pop_back(); + } + node.OnStack = false; } }; -void Dependency::AssertNoCycles() +/** + * Checks that adding these new dependencies to the configuration does not introduce any cycles. + * + * This is done as an optimization: cycles are checked once for all dependencies in a batch of config objects instead + * of individually per dependency in Dependency::OnAllConfigLoaded(). For runtime updates, this function may still be + * called for single objects. + * + * @param items Config items containing Dependency objects added to the running configuration. + */ +void Dependency::BeforeOnAllConfigLoadedHandler(const ConfigItems& items) { DependencyCycleChecker checker; - for (auto& host : ConfigType::GetObjectsByType()) { - checker.AssertNoCycle(host); - } + // Resolve parent/child names to Checkable::Ptr and temporarily add the edges to the checker. + // The dependencies are later registered to the checkables by Dependency::OnAllConfigLoaded(). + items.ForEachObject([&checker](Dependency::Ptr dependency) { + dependency->InitChildParentReferences(); + checker.AddExtraDependency(std::move(dependency)); + }); - for (auto& service : ConfigType::GetObjectsByType()) { - checker.AssertNoCycle(service); - } - - m_AssertNoCyclesForIndividualDeps = true; + // It's sufficient to search for cycles starting from newly added dependencies only: if a newly added dependency is + // part of a cycle, that cycle is reachable from both the child and the parent of that dependency. The cycle search + // is started from the parent as a slight optimization as that will traverse fewer edges if there is no cycle. + items.ForEachObject([&checker](const Dependency::Ptr& dependency) { + checker.AssertNoCycle(dependency->GetParent()); + }); } String DependencyNameComposer::MakeName(const String& shortName, const Object::Ptr& context) const @@ -178,10 +217,8 @@ void Dependency::OnConfigLoaded() SetStateFilter(FilterArrayToInt(GetStates(), Notification::GetStateFilterMap(), defaultFilter)); } -void Dependency::OnAllConfigLoaded() +void Dependency::InitChildParentReferences() { - ObjectImpl::OnAllConfigLoaded(); - Host::Ptr childHost = Host::GetByName(GetChildHostName()); if (childHost) { @@ -205,21 +242,17 @@ void Dependency::OnAllConfigLoaded() if (!m_Parent) BOOST_THROW_EXCEPTION(ScriptError("Dependency '" + GetName() + "' references a parent host/service which doesn't exist.", GetDebugInfo())); +} + +void Dependency::OnAllConfigLoaded() +{ + ObjectImpl::OnAllConfigLoaded(); + + // InitChildParentReferences() has to be called before. + VERIFY(m_Child && m_Parent); m_Child->AddDependency(this); m_Parent->AddReverseDependency(this); - - if (m_AssertNoCyclesForIndividualDeps) { - DependencyCycleChecker checker; - - try { - checker.AssertNoCycle(m_Parent); - } catch (...) { - m_Child->RemoveDependency(this); - m_Parent->RemoveReverseDependency(this); - throw; - } - } } void Dependency::Stop(bool runtimeRemoved) diff --git a/lib/icinga/dependency.hpp b/lib/icinga/dependency.hpp index 6cebfaab1..32bd8e70d 100644 --- a/lib/icinga/dependency.hpp +++ b/lib/icinga/dependency.hpp @@ -5,6 +5,7 @@ #include "icinga/i2-icinga.hpp" #include "icinga/dependency-ti.hpp" +#include "config/configitem.hpp" namespace icinga { @@ -22,6 +23,8 @@ class Service; class Dependency final : public ObjectImpl { public: + static void StaticInitialize(); + DECLARE_OBJECT(Dependency); DECLARE_OBJECTNAME(Dependency); @@ -36,9 +39,8 @@ public: static void EvaluateApplyRules(const intrusive_ptr& host); static void EvaluateApplyRules(const intrusive_ptr& service); - static void AssertNoCycles(); - /* Note: Only use them for unit test mocks. Prefer OnConfigLoaded(). */ + /* Note: Only use them for unit test mocks. Prefer InitChildParentReferences(). */ void SetParent(intrusive_ptr parent); void SetChild(intrusive_ptr child); @@ -46,15 +48,16 @@ protected: void OnConfigLoaded() override; void OnAllConfigLoaded() override; void Stop(bool runtimeRemoved) override; + void InitChildParentReferences(); private: Checkable::Ptr m_Parent; Checkable::Ptr m_Child; - static bool m_AssertNoCyclesForIndividualDeps; - static bool EvaluateApplyRuleInstance(const Checkable::Ptr& checkable, const String& name, ScriptFrame& frame, const ApplyRule& rule, bool skipFilter); static bool EvaluateApplyRule(const Checkable::Ptr& checkable, const ApplyRule& rule, bool skipFilter = false); + + static void BeforeOnAllConfigLoadedHandler(const ConfigItems& items); }; } From 4227d427da52c9cb4c5fe0bdcc793bc8fbde7e51 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 10 Mar 2025 15:48:34 +0100 Subject: [PATCH 176/415] .github/workflows/linux.bash: make $CMAKE_OPTS an array to have less to care about quoting. --- .github/workflows/linux.bash | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/linux.bash b/.github/workflows/linux.bash index a94f7d1b7..13f8a745e 100755 --- a/.github/workflows/linux.bash +++ b/.github/workflows/linux.bash @@ -4,7 +4,7 @@ set -exo pipefail export PATH="/usr/lib/ccache:/usr/lib64/ccache:$PATH" export CCACHE_DIR=/icinga2/ccache export CTEST_OUTPUT_ON_FAILURE=1 -CMAKE_OPTS='' +CMAKE_OPTS=() case "$DISTRO" in amazonlinux:2) @@ -24,7 +24,7 @@ case "$DISTRO" in ln -vs /usr/bin/cmake3 /usr/local/bin/cmake ln -vs /usr/bin/ninja-build /usr/local/bin/ninja - CMAKE_OPTS='-DBOOST_INCLUDEDIR=/boost_1_69_0 -DBOOST_LIBRARYDIR=/boost_1_69_0/stage/lib' + CMAKE_OPTS+=(-DBOOST_{INCLUDEDIR=/boost_1_69_0,LIBRARYDIR=/boost_1_69_0/stage/lib}) export LD_LIBRARY_PATH=/boost_1_69_0/stage/lib ;; @@ -76,7 +76,7 @@ cmake \ -DUSE_SYSTEMD=ON \ -DICINGA2_USER=$(id -un) \ -DICINGA2_GROUP=$(id -gn) \ - $CMAKE_OPTS .. + "${CMAKE_OPTS[@]}" .. ninja -v From f418d29379e77c6f85322682db9ad7a2799f78e6 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 12 Mar 2025 14:22:04 +0100 Subject: [PATCH 177/415] GHA: Linux: use the C(++) flags recommended by each respective distro --- .github/workflows/linux.bash | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/.github/workflows/linux.bash b/.github/workflows/linux.bash index 13f8a745e..1a2b5fb73 100755 --- a/.github/workflows/linux.bash +++ b/.github/workflows/linux.bash @@ -9,7 +9,7 @@ CMAKE_OPTS=() case "$DISTRO" in amazonlinux:2) amazon-linux-extras install -y epel - yum install -y bison ccache cmake3 gcc-c++ flex ninja-build \ + yum install -y bison ccache cmake3 gcc-c++ flex ninja-build system-rpm-config \ {libedit,mariadb,ncurses,openssl,postgresql,systemd}-devel yum install -y bzip2 tar wget @@ -29,23 +29,25 @@ case "$DISTRO" in ;; amazonlinux:20*) - dnf install -y bison cmake flex gcc-c++ ninja-build \ + dnf install -y amazon-rpm-config bison cmake flex gcc-c++ ninja-build \ {boost,libedit,mariadb1\*,ncurses,openssl,postgresql,systemd}-devel ;; debian:*|ubuntu:*) apt-get update - DEBIAN_FRONTEND=noninteractive apt-get install --no-install-{recommends,suggests} -y bison \ - ccache cmake flex g++ lib{boost-all,edit,mariadb,ncurses,pq,ssl,systemd}-dev ninja-build tzdata + DEBIAN_FRONTEND=noninteractive apt-get install --no-install-{recommends,suggests} -y \ + bison ccache cmake dpkg-dev flex g++ ninja-build tzdata \ + lib{boost-all,edit,mariadb,ncurses,pq,ssl,systemd}-dev ;; fedora:*) - dnf install -y bison ccache cmake flex gcc-c++ ninja-build \ + dnf install -y bison ccache cmake flex gcc-c++ ninja-build redhat-rpm-config \ {boost,libedit,mariadb,ncurses,openssl,postgresql,systemd}-devel ;; *suse*) - zypper in -y bison ccache cmake flex gcc-c++ ninja {lib{edit,mariadb,openssl},ncurses,postgresql,systemd}-devel \ + zypper in -y bison ccache cmake flex gcc-c++ ninja rpm-config-SUSE \ + {lib{edit,mariadb,openssl},ncurses,postgresql,systemd}-devel \ libboost_{context,coroutine,filesystem,iostreams,program_options,regex,system,test,thread}-devel ;; @@ -61,17 +63,28 @@ case "$DISTRO" in ;; esac - dnf install -y bison ccache cmake gcc-c++ flex ninja-build \ + dnf install -y bison ccache cmake gcc-c++ flex ninja-build redhat-rpm-config \ {boost,libedit,mariadb,ncurses,openssl,postgresql,systemd}-devel ;; esac +case "$DISTRO" in + debian:*|ubuntu:*) + CMAKE_OPTS+=(-DICINGA2_LTO_BUILD=ON) + source <(dpkg-buildflags --export=sh) + ;; + *) + CMAKE_OPTS+=(-DCMAKE_{C,CXX}_FLAGS="$(rpm -E '%{optflags} %{?march_flag}')") + export LDFLAGS="$(rpm -E '%{?build_ldflags}')" + ;; +esac + mkdir /icinga2/build cd /icinga2/build cmake \ -GNinja \ - -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -DICINGA2_UNITY_BUILD=ON \ -DUSE_SYSTEMD=ON \ -DICINGA2_USER=$(id -un) \ From ef93f945a241a32960525e51e9085d5079eeac77 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 21 Oct 2024 16:00:39 +0200 Subject: [PATCH 178/415] IcingaDB: Start keeping track of Host/Service to Dependency relationship This does not work in this state! Trying to refresh Dependency if a Host or Service being member of this Dependency has a state change. --- lib/icingadb/icingadb-objects.cpp | 200 ++++++++++++++++++++++++++++++ lib/icingadb/icingadb.cpp | 2 + lib/icingadb/icingadb.hpp | 3 + 3 files changed, 205 insertions(+) diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index 920251969..e46012ba1 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -19,6 +19,7 @@ #include "icinga/command.hpp" #include "icinga/compatutility.hpp" #include "icinga/customvarobject.hpp" +#include "icinga/dependency.hpp" #include "icinga/host.hpp" #include "icinga/service.hpp" #include "icinga/hostgroup.hpp" @@ -61,6 +62,7 @@ std::vector IcingaDB::GetTypes() // Then sync them for similar reasons. Downtime::TypeInstance, Comment::TypeInstance, + Dependency::TypeInstance, HostGroup::TypeInstance, ServiceGroup::TypeInstance, @@ -791,6 +793,137 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S return; } + if (type == Dependency::TypeInstance) { + auto& dependencyNodes (hMSets[m_PrefixConfigObject + "dependency:node"]); + auto& dependencyEdges (hMSets[m_PrefixConfigObject + "dependency:edge"]); + auto& redundancyGroups (hMSets[m_PrefixConfigObject + "redundancygroup"]); + + Dependency::Ptr dependency = static_pointer_cast(object); + + Host::Ptr parentHost, childHost; + Service::Ptr parentService, childService; + tie(parentHost, parentService) = GetHostService(dependency->GetParent()); + tie(childHost, childService) = GetHostService(dependency->GetChild()); + String redundancyGroup = dependency->GetRedundancyGroup(); + + String redundancyGroupId, dependencyNodeParentId, dependencyNodeChildId, dependencyNodeReduId; + + Dictionary::Ptr parentNodeData, childNodeData; + + if (parentService) { + dependencyNodeParentId = HashValue(new Array({ + m_EnvironmentId, + GetObjectIdentifier(parentHost), + GetObjectIdentifier(parentService)})); + parentNodeData = new Dictionary({ + {"environment_id", m_EnvironmentId}, + {"host_id", GetObjectIdentifier(parentHost)}, + {"service_id", GetObjectIdentifier(parentService)}}); + + m_CheckablesToDependencies->Set(GetObjectIdentifier(parentService), dependency); + } else { + dependencyNodeParentId = HashValue(new Array({ + m_EnvironmentId, + GetObjectIdentifier(parentHost)})); + parentNodeData = new Dictionary({ + {"environment_id", m_EnvironmentId}, + {"host_id", GetObjectIdentifier(parentHost)}}); + + m_CheckablesToDependencies->Set(GetObjectIdentifier(parentHost), dependency); + } + + if (childService) { + dependencyNodeChildId = HashValue(new Array({ + m_EnvironmentId, + GetObjectIdentifier(childHost), + GetObjectIdentifier(childService)})); + childNodeData = new Dictionary({ + {"environment_id", m_EnvironmentId}, + {"host_id", GetObjectIdentifier(childHost)}, + {"service_id", GetObjectIdentifier(childService)}}); + + m_CheckablesToDependencies->Set(GetObjectIdentifier(childService), dependency); + } else { + dependencyNodeChildId = HashValue(new Array({ + m_EnvironmentId, + GetObjectIdentifier(childHost)})); + childNodeData = new Dictionary({ + {"environment_id", m_EnvironmentId}, + {"host_id", GetObjectIdentifier(childHost)}}); + + m_CheckablesToDependencies->Set(GetObjectIdentifier(childHost), dependency); + } + + dependencyNodes.emplace_back(dependencyNodeParentId); + dependencyNodes.emplace_back(JsonEncode(parentNodeData)); + dependencyNodes.emplace_back(dependencyNodeChildId); + dependencyNodes.emplace_back(JsonEncode(childNodeData)); + + if (runtimeUpdate) { + AddObjectDataToRuntimeUpdates(runtimeUpdates, dependencyNodeParentId, m_PrefixConfigObject + "dependency:node", parentNodeData); + AddObjectDataToRuntimeUpdates(runtimeUpdates, dependencyNodeChildId, m_PrefixConfigObject + "dependency:node", childNodeData); + } + + if (!redundancyGroup.IsEmpty()) { + /* TODO: name should be suffixed with names of all children. + * however, at this point I don't have this information, + * only the direct neighbors. + */ + redundancyGroupId = HashValue(new Array({m_EnvironmentId, redundancyGroup, dependencyNodeChildId})); + dependencyNodeReduId = redundancyGroupId; + + redundancyGroups.emplace_back(redundancyGroupId); + Dictionary::Ptr groupData = new Dictionary({ + {"environment_id", m_EnvironmentId}, + {"name", redundancyGroupId}, + {"display_name", redundancyGroup}}); + redundancyGroups.emplace_back(JsonEncode(groupData)); + + dependencyNodes.emplace_back(dependencyNodeReduId); + Dictionary::Ptr reduNodeData = new Dictionary({ + {"environment_id", m_EnvironmentId}, + {"redundancy_group_id", redundancyGroupId}}); + dependencyNodes.emplace_back(JsonEncode(reduNodeData)); + + String edgeInId = HashValue(new Array({m_EnvironmentId, dependencyNodeChildId, dependencyNodeReduId})); + dependencyEdges.emplace_back(edgeInId); + Dictionary::Ptr edgeInData = new Dictionary({ + {"environment_id", m_EnvironmentId}, + {"from_node_id", dependencyNodeChildId}, + {"to_node_id", dependencyNodeReduId}}); + dependencyEdges.emplace_back(JsonEncode(edgeInData)); + + String edgeOutId = HashValue(new Array({m_EnvironmentId, dependencyNodeReduId, dependencyNodeParentId})); + dependencyEdges.emplace_back(edgeOutId); + Dictionary::Ptr edgeOutData = new Dictionary({ + {"environment_id", m_EnvironmentId}, + {"from_node_id", dependencyNodeReduId}, + {"to_node_id", dependencyNodeParentId}, + {"dependency_id", GetObjectIdentifier(dependency)}}); + dependencyEdges.emplace_back(JsonEncode(edgeOutData)); + + if (runtimeUpdate) { + AddObjectDataToRuntimeUpdates(runtimeUpdates, redundancyGroupId, m_PrefixConfigObject + "redundancygroup", groupData); + AddObjectDataToRuntimeUpdates(runtimeUpdates, dependencyNodeReduId, m_PrefixConfigObject + "dependency:node", reduNodeData); + AddObjectDataToRuntimeUpdates(runtimeUpdates, edgeInId, m_PrefixConfigObject + "dependency:edge", edgeInData); + AddObjectDataToRuntimeUpdates(runtimeUpdates, edgeOutId, m_PrefixConfigObject + "dependency:edge", edgeOutData); + } + } else { + String edgeId = HashValue(new Array({m_EnvironmentId, dependencyNodeChildId, dependencyNodeParentId})); + dependencyEdges.emplace_back(edgeId); + Dictionary::Ptr edgeData = new Dictionary({ + {"environment_id", m_EnvironmentId}, + {"from_node_id", dependencyNodeChildId}, + {"to_node_id", dependencyNodeParentId}, + {"dependency_id", GetObjectIdentifier(dependency)}}); + dependencyEdges.emplace_back(JsonEncode(edgeData)); + + if (runtimeUpdate) { + AddObjectDataToRuntimeUpdates(runtimeUpdates, edgeId, m_PrefixConfigObject + "dependency:edge", edgeData); + } + } + } + if (type == TimePeriod::TypeInstance) { TimePeriod::Ptr timeperiod = static_pointer_cast(object); @@ -1121,6 +1254,47 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S } } +void IcingaDB::UpdateDependencyState(const Dependency::Ptr& dependency) +{ + if (!m_Rcon || !m_Rcon->IsConnected()) { + return; + } + + auto& redundancyGroupStates (hMSets[m_PrefixConfigObject + "redundancygroup:state"]); + + String redundancyGroup = dependency->GetRedundancyGroup(); + + if (!redundancyGroup.IsEmpty()) { + Host::Ptr childHost; + Service::Ptr childService; + tie(childHost, childService) = GetHostService(dependency->GetChild()); + + String dependencyNodeChildId = HashValue( + (childService) + ? new Array({ m_EnvironmentId, GetObjectIdentifier(childHost), GetObjectIdentifier(childService) }) + : new Array({ m_EnvironmentId, GetObjectIdentifier(childHost) })); + String redundancyGroupId = HashValue(new Array({ + m_EnvironmentId, + redundancyGroup, + dependencyNodeChildId})); + + redundancyGroupStates.emplace_back(redundancyGroupId); + Dictionary::Ptr groupStateData = new Dictionary({ + {"environment_id", m_EnvironmentId}, + {"redundancy_group_id", redundancyGroupId}, + {"failed", !((childService) ? childService->IsReachable() : childHost->IsReachable())}, + {"last_state_change", TimestampToMilliseconds(Utility::GetTime())}}); + redundancyGroupStates.emplace_back(JsonEncode(groupStateData)); + + // TODO + // AddObjectDataToRuntimeUpdates(runtimeUpdates, redundancyGroupId, m_PrefixConfigObject + "redundancygroup:state", groupStateData); + // dataClone->Set("id", objectKey); // redundancyGroupId + // dataClone->Set("redis_key", redisKey); // m_PrefixConfigObject + "redundancygroup:state" + // dataClone->Set("runtime_type", "upsert"); + // runtimeUpdates.emplace_back(dataClone); + } +} + /** * Update the state information of a checkable in Redis. * @@ -1450,6 +1624,32 @@ bool IcingaDB::PrepareObject(const ConfigObject::Ptr& object, Dictionary::Ptr& a return true; } + if (type == Dependency::TypeInstance) { + Dependency::Ptr dependency = static_pointer_cast(object); + String redundancyGroup = dependency->GetRedundancyGroup(); + + attributes->Set("name", GetObjectIdentifier(dependency)); + + if (!redundancyGroup.IsEmpty()) { + Host::Ptr childHost; + Service::Ptr childService; + tie(childHost, childService) = GetHostService(dependency->GetChild()); + + String dependencyNodeChildId = HashValue( + (childService) + ? new Array({ m_EnvironmentId, GetObjectIdentifier(childHost), GetObjectIdentifier(childService) }) + : new Array({ m_EnvironmentId, GetObjectIdentifier(childHost) })); + String redundancyGroupId = HashValue(new Array({ + m_EnvironmentId, + redundancyGroup, + dependencyNodeChildId})); + + attributes->Set("redundancy_group_id", redundancyGroupId); + } + + return true; + } + if (type == Downtime::TypeInstance) { Downtime::Ptr downtime = static_pointer_cast(object); diff --git a/lib/icingadb/icingadb.cpp b/lib/icingadb/icingadb.cpp index 8d3b9099b..3c623259b 100644 --- a/lib/icingadb/icingadb.cpp +++ b/lib/icingadb/icingadb.cpp @@ -38,6 +38,8 @@ IcingaDB::IcingaDB() m_PrefixConfigObject = "icinga:"; m_PrefixConfigCheckSum = "icinga:checksum:"; + + m_CheckablesToDependencies = new Dictionary(); } void IcingaDB::Validate(int types, const ValidationUtils& utils) diff --git a/lib/icingadb/icingadb.hpp b/lib/icingadb/icingadb.hpp index b943d7f4b..c5b499318 100644 --- a/lib/icingadb/icingadb.hpp +++ b/lib/icingadb/icingadb.hpp @@ -103,6 +103,7 @@ private: std::vector GetTypeDumpSignalKeys(const Type::Ptr& type); void InsertObjectDependencies(const ConfigObject::Ptr& object, const String typeName, std::map>& hMSets, std::vector& runtimeUpdates, bool runtimeUpdate); + void UpdateDependencyState(const Dependency::Ptr& dependency); void UpdateState(const Checkable::Ptr& checkable, StateUpdate mode); void SendConfigUpdate(const ConfigObject::Ptr& object, bool runtimeUpdate); void CreateConfigUpdate(const ConfigObject::Ptr& object, const String type, std::map>& hMSets, @@ -224,6 +225,8 @@ private: std::unordered_map m_Rcons; std::atomic_size_t m_PendingRcons; + Dictionary::Ptr m_CheckablesToDependencies; + struct { DumpedGlobals CustomVar, ActionUrl, NotesUrl, IconImage; } m_DumpedGlobals; From d6b289e1cde1740db7a04810bdb38e81c4e88b76 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 2 Dec 2024 14:15:00 +0100 Subject: [PATCH 179/415] Checkable: Introduce `GetAllChildrenCount()` method The previous limit (32) doesn't seem to make sense, and appears to be some random number. So, this limit is set to 256 to match the limit in IsReachable(). --- lib/icinga/checkable-dependency.cpp | 55 +++++++++++++++++++++++------ lib/icinga/checkable.hpp | 1 + 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/lib/icinga/checkable-dependency.cpp b/lib/icinga/checkable-dependency.cpp index 58d6b578b..a58b55c05 100644 --- a/lib/icinga/checkable-dependency.cpp +++ b/lib/icinga/checkable-dependency.cpp @@ -7,6 +7,14 @@ using namespace icinga; +/** + * The maximum number of dependency recursion levels allowed. + * + * This is a subjective limit how deep the dependency tree should be allowed to go, as anything beyond this level + * is just madness and will likely result in a stack overflow or other undefined behavior. + */ +static constexpr int l_MaxDependencyRecursionLevel(256); + void Checkable::AddDependency(const Dependency::Ptr& dep) { std::unique_lock lock(m_DependencyMutex); @@ -45,12 +53,9 @@ std::vector Checkable::GetReverseDependencies() const bool Checkable::IsReachable(DependencyType dt, Dependency::Ptr *failedDependency, int rstack) const { - /* Anything greater than 256 causes recursion bus errors. */ - int limit = 256; - - if (rstack > limit) { + if (rstack > l_MaxDependencyRecursionLevel) { Log(LogWarning, "Checkable") - << "Too many nested dependencies (>" << limit << ") for checkable '" << GetName() << "': Dependency failed."; + << "Too many nested dependencies (>" << l_MaxDependencyRecursionLevel << ") for checkable '" << GetName() << "': Dependency failed."; return false; } @@ -145,6 +150,22 @@ std::set Checkable::GetChildren() const return parents; } +/** + * Retrieve the total number of all the children of the current Checkable. + * + * Note, due to the max recursion limit of 256, the returned number may not reflect + * the actual total number of children involved in the dependency chain. + * + * @return int - Returns the total number of all the children of the current Checkable. + */ +size_t Checkable::GetAllChildrenCount() const +{ + // Are you thinking in making this more efficient? Please, don't. + // In order not to count the same child multiple times, we need to maintain a separate set of visited children, + // which is basically the same as what GetAllChildren() does. So, we're using it here! + return GetAllChildren().size(); +} + std::set Checkable::GetAllChildren() const { std::set children = GetChildren(); @@ -154,22 +175,34 @@ std::set Checkable::GetAllChildren() const return children; } +/** + * Retrieve all direct and indirect children of the current Checkable. + * + * Note, this function performs a recursive call chain traversing all the children of the current Checkable + * up to a certain limit (256). When that limit is reached, it will log a warning message and abort the operation. + * + * @param children - The set of children to be filled with all the children of the current Checkable. + * @param level - The current level of recursion. + */ void Checkable::GetAllChildrenInternal(std::set& children, int level) const { - if (level > 32) - return; + if (level > l_MaxDependencyRecursionLevel) { + Log(LogWarning, "Checkable") + << "Too many nested dependencies (>" << l_MaxDependencyRecursionLevel << ") for checkable '" << GetName() << "': aborting traversal."; + return ; + } std::set localChildren; for (const Checkable::Ptr& checkable : children) { - std::set cChildren = checkable->GetChildren(); - - if (!cChildren.empty()) { + if (auto cChildren(checkable->GetChildren()); !cChildren.empty()) { GetAllChildrenInternal(cChildren, level + 1); localChildren.insert(cChildren.begin(), cChildren.end()); } - localChildren.insert(checkable); + if (level != 0) { // Recursion level 0 is the initiator, so checkable is already in the set. + localChildren.insert(checkable); + } } children.insert(localChildren.begin(), localChildren.end()); diff --git a/lib/icinga/checkable.hpp b/lib/icinga/checkable.hpp index fcfbca9b2..c9e54b0f5 100644 --- a/lib/icinga/checkable.hpp +++ b/lib/icinga/checkable.hpp @@ -77,6 +77,7 @@ public: std::set GetParents() const; std::set GetChildren() const; std::set GetAllChildren() const; + size_t GetAllChildrenCount() const; void AddGroup(const String& name); From 297b62d841797c8d635a37834a2e81f1f8da2926 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 2 Dec 2024 14:19:55 +0100 Subject: [PATCH 180/415] IcingaDB: Add `affected_children` to `Host/Service` Redis updates --- lib/icingadb/icingadb-objects.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index e46012ba1..ac111c745 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -1482,6 +1482,11 @@ bool IcingaDB::PrepareObject(const ConfigObject::Ptr& object, Dictionary::Ptr& a attributes->Set("notes", checkable->GetNotes()); attributes->Set("icon_image_alt", checkable->GetIconImageAlt()); + if (size_t totalChildren (checkable->GetAllChildrenCount()); totalChildren > 0) { + // Only set the Redis key if the Checkable has actually some child dependencies. + attributes->Set("total_children", totalChildren); + } + attributes->Set("checkcommand_id", GetObjectIdentifier(checkable->GetCheckCommand())); Endpoint::Ptr commandEndpoint = checkable->GetCommandEndpoint(); From 632160667171041177e7a16320b7ffbdb667a3f7 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 2 Dec 2024 14:37:09 +0100 Subject: [PATCH 181/415] IcingaDB: Sync `affects_children` as part of runtime state updates --- lib/icinga/checkable-dependency.cpp | 29 +++++++++++++++++++++++++++++ lib/icinga/checkable.hpp | 1 + lib/icingadb/icingadb-objects.cpp | 1 + 3 files changed, 31 insertions(+) diff --git a/lib/icinga/checkable-dependency.cpp b/lib/icinga/checkable-dependency.cpp index a58b55c05..d10d6f3ce 100644 --- a/lib/icinga/checkable-dependency.cpp +++ b/lib/icinga/checkable-dependency.cpp @@ -122,6 +122,35 @@ bool Checkable::IsReachable(DependencyType dt, Dependency::Ptr *failedDependency return true; } +/** + * Checks whether the last check result of this Checkable affects its child dependencies. + * + * A Checkable affects its child dependencies if it runs into a non-OK state and results in any of its child + * Checkables to become unreachable. Though, that unavailable dependency may not necessarily cause the child + * Checkable to be in unreachable state as it might have some other dependencies that are still reachable, instead + * it just indicates whether the edge/connection between this and the child Checkable is broken or not. + * + * @return bool - Returns true if the Checkable affects its child dependencies, otherwise false. + */ +bool Checkable::AffectsChildren() const +{ + if (!GetLastCheckResult() || !IsReachable()) { + // If there is no check result, or the Checkable is not reachable, we can't safely determine whether + // the Checkable affects its child dependencies. + return false; + } + + for (auto& dep : GetReverseDependencies()) { + if (!dep->IsAvailable(DependencyState)) { + // If one of the child dependency is not available, then it's definitely due to the + // current Checkable state, so we don't need to verify the remaining ones. + return true; + } + } + + return false; +} + std::set Checkable::GetParents() const { std::set parents; diff --git a/lib/icinga/checkable.hpp b/lib/icinga/checkable.hpp index c9e54b0f5..c6413fa1b 100644 --- a/lib/icinga/checkable.hpp +++ b/lib/icinga/checkable.hpp @@ -82,6 +82,7 @@ public: void AddGroup(const String& name); bool IsReachable(DependencyType dt = DependencyState, intrusive_ptr *failedDependency = nullptr, int rstack = 0) const; + bool AffectsChildren() const; AcknowledgementType GetAcknowledgement(); diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index ac111c745..a7618e75a 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -2828,6 +2828,7 @@ Dictionary::Ptr IcingaDB::SerializeState(const Checkable::Ptr& checkable) attrs->Set("check_attempt", checkable->GetCheckAttempt()); attrs->Set("is_active", checkable->IsActive()); + attrs->Set("affects_children", checkable->AffectsChildren()); CheckResult::Ptr cr = checkable->GetLastCheckResult(); From c64ae1af0fcde711ded92366fd82de9cddf9bbf7 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 5 Dec 2024 09:09:31 +0100 Subject: [PATCH 182/415] Dependency: Don't allow to change `redundancy_group` at runtime Otherwise, it would require too much code changes to properly handle redundancy group runtime modification in Icinga DB for no real benefit. --- lib/icinga/dependency.ti | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/icinga/dependency.ti b/lib/icinga/dependency.ti index 41de7ba23..38f5859ae 100644 --- a/lib/icinga/dependency.ti +++ b/lib/icinga/dependency.ti @@ -77,7 +77,7 @@ class Dependency : CustomVarObject < DependencyNameComposer }}} }; - [config] String redundancy_group; + [config, no_user_modify] String redundancy_group; [config, navigation] name(TimePeriod) period (PeriodRaw) { navigate {{{ From 772420a438537556e9c6b769ea49a281688e7fd7 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 6 Feb 2025 08:58:03 +0100 Subject: [PATCH 183/415] Checkable: Don't always trigger reachablity changed signal But only when the current check result being processed affects the child Checkables in any way. --- lib/icinga/checkable-check.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/icinga/checkable-check.cpp b/lib/icinga/checkable-check.cpp index 6e3b8764b..31993fc87 100644 --- a/lib/icinga/checkable-check.cpp +++ b/lib/icinga/checkable-check.cpp @@ -154,6 +154,10 @@ Checkable::ProcessingResult Checkable::ProcessCheckResult(const CheckResult::Ptr bool reachable = IsReachable(); bool notification_reachable = IsReachable(DependencyNotification); + // Cache whether the previous state of this Checkable affects its children before overwriting the last check result. + // This will be used to determine whether the on reachability changed event should be triggered. + bool affectsPreviousStateChildren(reachable && AffectsChildren()); + ObjectLock olock(this); CheckResult::Ptr old_cr = GetLastCheckResult(); @@ -533,7 +537,7 @@ Checkable::ProcessingResult Checkable::ProcessCheckResult(const CheckResult::Ptr } /* update reachability for child objects */ - if ((stateChange || hardChange) && !children.empty()) + if ((stateChange || hardChange) && !children.empty() && (affectsPreviousStateChildren || AffectsChildren())) OnReachabilityChanged(this, cr, children, origin); return Result::Ok; From c02b9d74a9e42a168dc9e5509d55d9766ecff5ac Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 15 Jan 2025 17:27:28 +0100 Subject: [PATCH 184/415] IcingaDB: Send reachablity state updates for all children --- lib/icingadb/icingadb-objects.cpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index a7618e75a..cbdc48026 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -96,8 +96,11 @@ void IcingaDB::ConfigStaticInitialize() AcknowledgementClearedHandler(checkable, removedBy, changeTime); }); - Checkable::OnReachabilityChanged.connect([](const Checkable::Ptr&, const CheckResult::Ptr&, std::set children, const MessageOrigin::Ptr&) { - IcingaDB::ReachabilityChangeHandler(children); + Checkable::OnReachabilityChanged.connect([](const Checkable::Ptr& parent, const CheckResult::Ptr&, std::set, const MessageOrigin::Ptr&) { + // Icinga DB Web needs to know about the reachability of all children, not just the direct ones. + // These might get updated with their next check result anyway, but we can't rely on that, since + // they might not be actively checked or have a very high check interval. + IcingaDB::ReachabilityChangeHandler(parent->GetAllChildren()); }); /* triggered on create, update and delete objects */ From e0ce0ccff6ecb489794bd9739be8249d8b6b1f01 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 10 Jan 2025 13:24:27 +0100 Subject: [PATCH 185/415] Activate `Dependency` objects before their parent objects --- lib/icinga/dependency.ti | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/icinga/dependency.ti b/lib/icinga/dependency.ti index 38f5859ae..b58877104 100644 --- a/lib/icinga/dependency.ti +++ b/lib/icinga/dependency.ti @@ -20,6 +20,8 @@ public: class Dependency : CustomVarObject < DependencyNameComposer { + activation_priority -10; + load_after Host; load_after Service; From c465f45200698b3c81d632fa5cfdfc1107507332 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 31 Jan 2025 16:30:13 +0100 Subject: [PATCH 186/415] Rewrite `Checkable::GetAllChildrenInternal()` method The previous wasn't per-se wrong, but it was way too inefficient. With this commit each and every Checkable is going to be visited only once, and we won't traverse the same Checkable's children multiple times somewhere in the dependency chain. --- lib/icinga/checkable-dependency.cpp | 26 +++++++++----------------- lib/icinga/checkable.hpp | 2 +- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/lib/icinga/checkable-dependency.cpp b/lib/icinga/checkable-dependency.cpp index d10d6f3ce..40d66f59f 100644 --- a/lib/icinga/checkable-dependency.cpp +++ b/lib/icinga/checkable-dependency.cpp @@ -8,7 +8,7 @@ using namespace icinga; /** - * The maximum number of dependency recursion levels allowed. + * The maximum number of allowed dependency recursion levels. * * This is a subjective limit how deep the dependency tree should be allowed to go, as anything beyond this level * is just madness and will likely result in a stack overflow or other undefined behavior. @@ -197,7 +197,7 @@ size_t Checkable::GetAllChildrenCount() const std::set Checkable::GetAllChildren() const { - std::set children = GetChildren(); + std::set children; GetAllChildrenInternal(children, 0); @@ -210,29 +210,21 @@ std::set Checkable::GetAllChildren() const * Note, this function performs a recursive call chain traversing all the children of the current Checkable * up to a certain limit (256). When that limit is reached, it will log a warning message and abort the operation. * - * @param children - The set of children to be filled with all the children of the current Checkable. + * @param seenChildren - A container to store all the traversed children into. * @param level - The current level of recursion. */ -void Checkable::GetAllChildrenInternal(std::set& children, int level) const +void Checkable::GetAllChildrenInternal(std::set& seenChildren, int level) const { if (level > l_MaxDependencyRecursionLevel) { Log(LogWarning, "Checkable") << "Too many nested dependencies (>" << l_MaxDependencyRecursionLevel << ") for checkable '" << GetName() << "': aborting traversal."; - return ; + return; } - std::set localChildren; - - for (const Checkable::Ptr& checkable : children) { - if (auto cChildren(checkable->GetChildren()); !cChildren.empty()) { - GetAllChildrenInternal(cChildren, level + 1); - localChildren.insert(cChildren.begin(), cChildren.end()); - } - - if (level != 0) { // Recursion level 0 is the initiator, so checkable is already in the set. - localChildren.insert(checkable); + for (const Checkable::Ptr& checkable : GetChildren()) { + if (auto [_, inserted] = seenChildren.insert(checkable); inserted) { + seenChildren.emplace(checkable); + checkable->GetAllChildrenInternal(seenChildren, level + 1); } } - - children.insert(localChildren.begin(), localChildren.end()); } diff --git a/lib/icinga/checkable.hpp b/lib/icinga/checkable.hpp index c6413fa1b..12e620ed5 100644 --- a/lib/icinga/checkable.hpp +++ b/lib/icinga/checkable.hpp @@ -249,7 +249,7 @@ private: std::set > m_Dependencies; std::set > m_ReverseDependencies; - void GetAllChildrenInternal(std::set& children, int level = 0) const; + void GetAllChildrenInternal(std::set& seenChildren, int level = 0) const; /* Flapping */ static const std::map m_FlappingStateFilterMap; From 67664ad7b7a5526606e0d1059e0be9be0940fa11 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Thu, 6 Feb 2025 15:46:24 +0100 Subject: [PATCH 187/415] Checkable::GetAllChildrenInternal: remove redundant emplace call `checkable` is already added to the set by the insert call above, so calling emplace for the same checkable doesn't do anything useful and can be removed. --- lib/icinga/checkable-dependency.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/icinga/checkable-dependency.cpp b/lib/icinga/checkable-dependency.cpp index 40d66f59f..a302f3aec 100644 --- a/lib/icinga/checkable-dependency.cpp +++ b/lib/icinga/checkable-dependency.cpp @@ -223,7 +223,6 @@ void Checkable::GetAllChildrenInternal(std::set& seenChildren, i for (const Checkable::Ptr& checkable : GetChildren()) { if (auto [_, inserted] = seenChildren.insert(checkable); inserted) { - seenChildren.emplace(checkable); checkable->GetAllChildrenInternal(seenChildren, level + 1); } } From 93d9fad5654dfa3fabe085f16b0e20ca224fb4a5 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 12 Dec 2024 18:31:05 +0100 Subject: [PATCH 188/415] Checkable: Drop unused `failedDependency` argument from `IsReachable()` --- lib/icinga/checkable-dependency.cpp | 16 ++-------------- lib/icinga/checkable.hpp | 2 +- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/lib/icinga/checkable-dependency.cpp b/lib/icinga/checkable-dependency.cpp index a302f3aec..f8a84a56b 100644 --- a/lib/icinga/checkable-dependency.cpp +++ b/lib/icinga/checkable-dependency.cpp @@ -51,7 +51,7 @@ std::vector Checkable::GetReverseDependencies() const return std::vector(m_ReverseDependencies.begin(), m_ReverseDependencies.end()); } -bool Checkable::IsReachable(DependencyType dt, Dependency::Ptr *failedDependency, int rstack) const +bool Checkable::IsReachable(DependencyType dt, int rstack) const { if (rstack > l_MaxDependencyRecursionLevel) { Log(LogWarning, "Checkable") @@ -61,7 +61,7 @@ bool Checkable::IsReachable(DependencyType dt, Dependency::Ptr *failedDependency } for (const Checkable::Ptr& checkable : GetParents()) { - if (!checkable->IsReachable(dt, failedDependency, rstack + 1)) + if (!checkable->IsReachable(dt, rstack + 1)) return false; } @@ -71,9 +71,6 @@ bool Checkable::IsReachable(DependencyType dt, Dependency::Ptr *failedDependency Host::Ptr host = service->GetHost(); if (host && host->GetState() != HostUp && host->GetStateType() == StateTypeHard) { - if (failedDependency) - *failedDependency = nullptr; - return false; } } @@ -90,9 +87,6 @@ bool Checkable::IsReachable(DependencyType dt, Dependency::Ptr *failedDependency Log(LogDebug, "Checkable") << "Non-redundant dependency '" << dep->GetName() << "' failed for checkable '" << GetName() << "': Marking as unreachable."; - if (failedDependency) - *failedDependency = dep; - return false; } @@ -110,15 +104,9 @@ bool Checkable::IsReachable(DependencyType dt, Dependency::Ptr *failedDependency Log(LogDebug, "Checkable") << "All dependencies in redundancy group '" << violator->first << "' have failed for checkable '" << GetName() << "': Marking as unreachable."; - if (failedDependency) - *failedDependency = violator->second; - return false; } - if (failedDependency) - *failedDependency = nullptr; - return true; } diff --git a/lib/icinga/checkable.hpp b/lib/icinga/checkable.hpp index 12e620ed5..e34b34ef0 100644 --- a/lib/icinga/checkable.hpp +++ b/lib/icinga/checkable.hpp @@ -81,7 +81,7 @@ public: void AddGroup(const String& name); - bool IsReachable(DependencyType dt = DependencyState, intrusive_ptr *failedDependency = nullptr, int rstack = 0) const; + bool IsReachable(DependencyType dt = DependencyState, int rstack = 0) const; bool AffectsChildren() const; AcknowledgementType GetAcknowledgement(); From d7c9e6687e8aefc0f84fcea448b4053531403530 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 5 Dec 2024 10:42:21 +0100 Subject: [PATCH 189/415] Introduce `DependencyGroup` helper class --- lib/icinga/CMakeLists.txt | 2 +- lib/icinga/checkable-dependency.cpp | 12 + lib/icinga/checkable.hpp | 4 + lib/icinga/dependency-group.cpp | 340 ++++++++++++++++++++++++++++ lib/icinga/dependency.hpp | 143 +++++++++++- 5 files changed, 499 insertions(+), 2 deletions(-) create mode 100644 lib/icinga/dependency-group.cpp diff --git a/lib/icinga/CMakeLists.txt b/lib/icinga/CMakeLists.txt index 62077bce7..8187d48e8 100644 --- a/lib/icinga/CMakeLists.txt +++ b/lib/icinga/CMakeLists.txt @@ -39,7 +39,7 @@ set(icinga_SOURCES comment.cpp comment.hpp comment-ti.hpp compatutility.cpp compatutility.hpp customvarobject.cpp customvarobject.hpp customvarobject-ti.hpp - dependency.cpp dependency.hpp dependency-ti.hpp dependency-apply.cpp + dependency.cpp dependency-group.cpp dependency.hpp dependency-ti.hpp dependency-apply.cpp downtime.cpp downtime.hpp downtime-ti.hpp envresolver.cpp envresolver.hpp eventcommand.cpp eventcommand.hpp eventcommand-ti.hpp diff --git a/lib/icinga/checkable-dependency.cpp b/lib/icinga/checkable-dependency.cpp index f8a84a56b..8a838a7d5 100644 --- a/lib/icinga/checkable-dependency.cpp +++ b/lib/icinga/checkable-dependency.cpp @@ -15,6 +15,18 @@ using namespace icinga; */ static constexpr int l_MaxDependencyRecursionLevel(256); +void Checkable::AddDependencyGroup(const DependencyGroup::Ptr& dependencyGroup) +{ + std::unique_lock lock(m_DependencyMutex); + m_DependencyGroups.insert(dependencyGroup); +} + +void Checkable::RemoveDependencyGroup(const DependencyGroup::Ptr& dependencyGroup) +{ + std::unique_lock lock(m_DependencyMutex); + m_DependencyGroups.erase(dependencyGroup); +} + void Checkable::AddDependency(const Dependency::Ptr& dep) { std::unique_lock lock(m_DependencyMutex); diff --git a/lib/icinga/checkable.hpp b/lib/icinga/checkable.hpp index e34b34ef0..39cc8f570 100644 --- a/lib/icinga/checkable.hpp +++ b/lib/icinga/checkable.hpp @@ -57,6 +57,7 @@ enum FlappingStateFilter class CheckCommand; class EventCommand; class Dependency; +class DependencyGroup; /** * An Icinga service. @@ -184,6 +185,8 @@ public: bool IsFlapping() const; /* Dependencies */ + void AddDependencyGroup(const intrusive_ptr& dependencyGroup); + void RemoveDependencyGroup(const intrusive_ptr& dependencyGroup); void AddDependency(const intrusive_ptr& dep); void RemoveDependency(const intrusive_ptr& dep); std::vector > GetDependencies() const; @@ -246,6 +249,7 @@ private: /* Dependencies */ mutable std::mutex m_DependencyMutex; + std::set> m_DependencyGroups; std::set > m_Dependencies; std::set > m_ReverseDependencies; diff --git a/lib/icinga/dependency-group.cpp b/lib/icinga/dependency-group.cpp new file mode 100644 index 000000000..7d686fc0f --- /dev/null +++ b/lib/icinga/dependency-group.cpp @@ -0,0 +1,340 @@ +/* Icinga 2 | (c) 2024 Icinga GmbH | GPLv2+ */ + +#include "icinga/dependency.hpp" +#include "base/object-packer.hpp" + +using namespace icinga; + +std::mutex DependencyGroup::m_RegistryMutex; +DependencyGroup::RegistryType DependencyGroup::m_Registry; + +/** + * Refresh the global registry of dependency groups. + * + * Registers the provided dependency object to an existing dependency group with the same redundancy + * group name (if any), or creates a new one and registers it to the child Checkable and the registry. + * + * Note: This is a helper function intended for internal use only, and you should acquire the global registry mutex + * before calling this function. + * + * @param dependency The dependency object to refresh the registry for. + * @param unregister A flag indicating whether the provided dependency object should be unregistered from the registry. + */ +void DependencyGroup::RefreshRegistry(const Dependency::Ptr& dependency, bool unregister) +{ + auto registerRedundancyGroup = [](const DependencyGroup::Ptr& dependencyGroup) { + if (auto [it, inserted](m_Registry.insert(dependencyGroup.get())); !inserted) { + DependencyGroup::Ptr existingGroup(*it); + dependencyGroup->CopyDependenciesTo(existingGroup); + } + }; + + // Retrieve all the dependency groups with the same redundancy group name of the provided dependency object. + // This allows us to shorten the lookup for the _one_ optimal group to (un)register the dependency from/to. + auto [begin, end] = m_Registry.get<1>().equal_range(dependency->GetRedundancyGroup()); + for (auto it(begin); it != end; ++it) { + DependencyGroup::Ptr existingGroup(*it); + auto child(dependency->GetChild()); + if (auto dependencies(existingGroup->GetDependenciesForChild(child.get())); !dependencies.empty()) { + m_Registry.erase(existingGroup->GetCompositeKey()); // Will be re-registered when needed down below. + if (unregister) { + existingGroup->RemoveDependency(dependency); + // Remove the connection between the child Checkable and the dependency group if it has no members + // left or the above removed member was the only member of the group that the child depended on. + if (existingGroup->IsEmpty() || dependencies.size() == 1) { + child->RemoveDependencyGroup(existingGroup); + } + } + + size_t totalDependencies(existingGroup->GetDependenciesCount()); + // If the existing dependency group has an identical member already, or the child Checkable of the + // dependency object is the only member of it (totalDependencies == dependencies.size()), we can simply + // add the dependency object to the existing group. + if (!unregister && (existingGroup->HasParentWithConfig(dependency) || totalDependencies == dependencies.size())) { + existingGroup->AddDependency(dependency); + } else if (!unregister || (dependencies.size() > 1 && totalDependencies >= dependencies.size())) { + // The child Checkable is going to have a new dependency group, so we must detach the existing one. + child->RemoveDependencyGroup(existingGroup); + + Ptr replacementGroup(unregister ? nullptr : new DependencyGroup(existingGroup->GetRedundancyGroupName(), dependency)); + for (auto& existingDependency : dependencies) { + if (existingDependency != dependency) { + existingGroup->RemoveDependency(existingDependency); + if (replacementGroup) { + replacementGroup->AddDependency(existingDependency); + } else { + replacementGroup = new DependencyGroup(existingGroup->GetRedundancyGroupName(), existingDependency); + } + } + } + + child->AddDependencyGroup(replacementGroup); + registerRedundancyGroup(replacementGroup); + } + + if (!existingGroup->IsEmpty()) { + registerRedundancyGroup(existingGroup); + } + return; + } + } + + if (!unregister) { + // We couldn't find any existing dependency group to register the dependency to, so we must + // initiate a new one and attach it to the child Checkable and register to the global registry. + DependencyGroup::Ptr newGroup(new DependencyGroup(dependency->GetRedundancyGroup())); + newGroup->AddDependency(dependency); + dependency->GetChild()->AddDependencyGroup(newGroup); + registerRedundancyGroup(newGroup); + } +} + +/** + * Register the provided dependency to the global dependency group registry. + * + * @param dependency The dependency to register. + */ +void DependencyGroup::Register(const Dependency::Ptr& dependency) +{ + std::lock_guard lock(m_RegistryMutex); + RefreshRegistry(dependency, false); +} + +/** + * Unregister the provided dependency from the dependency group it was member of. + * + * @param dependency The dependency to unregister. + */ +void DependencyGroup::Unregister(const Dependency::Ptr& dependency) +{ + std::lock_guard lock(m_RegistryMutex); + RefreshRegistry(dependency, true); +} + +/** + * Retrieve the size of the global dependency group registry. + * + * @return size_t - Returns the size of the global dependency groups registry. + */ +size_t DependencyGroup::GetRegistrySize() +{ + std::lock_guard lock(m_RegistryMutex); + return m_Registry.size(); +} + +DependencyGroup::DependencyGroup(String name): m_RedundancyGroupName(std::move(name)) +{ +} + +/** + * Create a composite key for the provided dependency. + * + * The composite key consists of all the properties of the provided dependency object that influence its availability. + * + * @param dependency The dependency object to create a composite key for. + * + * @return - Returns the composite key for the provided dependency. + */ +DependencyGroup::CompositeKeyType DependencyGroup::MakeCompositeKeyFor(const Dependency::Ptr& dependency) +{ + return std::make_tuple( + dependency->GetParent().get(), + dependency->GetPeriod().get(), + dependency->GetStateFilter(), + dependency->GetIgnoreSoftStates() + ); +} + +/** + * Check if the current dependency group is empty. + * + * @return bool - Returns true if the current dependency group has no members, otherwise false. + */ +bool DependencyGroup::IsEmpty() const +{ + std::lock_guard lock(m_Mutex); + return m_Members.empty(); +} + +/** + * Retrieve all dependency objects of the current dependency group the provided child Checkable depend on. + * + * @param child The child Checkable to get the dependencies for. + * + * @return - Returns all the dependencies of the provided child Checkable in the current dependency group. + */ +std::vector DependencyGroup::GetDependenciesForChild(const Checkable* child) const +{ + std::lock_guard lock(m_Mutex); + std::vector dependencies; + for (auto& [_, children] : m_Members) { + auto [begin, end] = children.equal_range(child); + std::transform(begin, end, std::back_inserter(dependencies), [](const auto& pair) { + return pair.second; + }); + } + return dependencies; +} + +/** + * Retrieve the number of dependency objects in the current dependency group. + * + * This function mainly exists for optimization purposes, i.e. instead of getting a copy of the members and + * counting them, we can directly query the number of dependencies in the group. + * + * @return size_t + */ +size_t DependencyGroup::GetDependenciesCount() const +{ + std::lock_guard lock(m_Mutex); + size_t count(0); + for (auto& [_, dependencies] : m_Members) { + count += dependencies.size(); + } + return count; +} + +/** + * Add a dependency object to the current dependency group. + * + * @param dependency The dependency to add to the dependency group. + */ +void DependencyGroup::AddDependency(const Dependency::Ptr& dependency) +{ + std::lock_guard lock(m_Mutex); + auto compositeKey(MakeCompositeKeyFor(dependency)); + if (auto it(m_Members.find(compositeKey)); it != m_Members.end()) { + it->second.emplace(dependency->GetChild().get(), dependency.get()); + } else { + m_Members.emplace(compositeKey, MemberValueType{{dependency->GetChild().get(), dependency.get()}}); + } +} + +/** + * Remove a dependency object from the current dependency group. + * + * @param dependency The dependency to remove from the dependency group. + */ +void DependencyGroup::RemoveDependency(const Dependency::Ptr& dependency) +{ + std::lock_guard lock(m_Mutex); + if (auto it(m_Members.find(MakeCompositeKeyFor(dependency))); it != m_Members.end()) { + auto [begin, end] = it->second.equal_range(dependency->GetChild().get()); + for (auto childrenIt(begin); childrenIt != end; ++childrenIt) { + if (childrenIt->second == dependency) { + // This will also remove the child Checkable from the multimap container + // entirely if this was the last child of it. + it->second.erase(childrenIt); + // If the composite key has no more children left, we can remove it entirely as well. + if (it->second.empty()) { + m_Members.erase(it); + } + return; + } + } + } +} + +/** + * Copy the dependency objects of the current dependency group to the provided dependency group (destination). + * + * @param dest The dependency group to move the dependencies to. + */ +void DependencyGroup::CopyDependenciesTo(const DependencyGroup::Ptr& dest) +{ + VERIFY(this != dest); // Prevent from doing something stupid, i.e. deadlocking ourselves. + + std::lock_guard lock(m_Mutex); + DependencyGroup::Ptr thisPtr(this); // Just in case the Checkable below was our last reference. + for (auto& [_, children] : m_Members) { + Checkable::Ptr previousChild; + for (auto& [checkable, dependency] : children) { + dest->AddDependency(dependency); + if (!previousChild || previousChild != checkable) { + previousChild = dependency->GetChild(); + previousChild->RemoveDependencyGroup(thisPtr); + previousChild->AddDependencyGroup(dest); + } + } + } +} + +/** + * Set the Icinga DB identifier for the current dependency group. + * + * The only usage of this function is the Icinga DB feature used to cache the unique hash of this dependency groups. + * + * @param identifier The Icinga DB identifier to set. + */ +void DependencyGroup::SetIcingaDBIdentifier(const String& identifier) +{ + std::lock_guard lock(m_Mutex); + m_IcingaDBIdentifier = identifier; +} + +/** + * Retrieve the Icinga DB identifier for the current dependency group. + * + * When the identifier is not already set by Icinga DB via the SetIcingaDBIdentifier method, + * this will just return an empty string. + * + * @return - Returns the Icinga DB identifier for the current dependency group. + */ +String DependencyGroup::GetIcingaDBIdentifier() const +{ + std::lock_guard lock(m_Mutex); + return m_IcingaDBIdentifier; +} + +/** + * Retrieve the redundancy group name of the current dependency group. + * + * If the current dependency group doesn't represent a redundancy group, this will return an empty string. + * + * @return - Returns the name of the current dependency group. + */ +const String& DependencyGroup::GetRedundancyGroupName() const +{ + // We don't need to lock the mutex here, as the name is set once during + // the object construction and never changed afterwards. + return m_RedundancyGroupName; +} + +/** + * Retrieve the unique composite key of the current dependency group. + * + * The composite key consists of some unique data of the group members, and should be used to generate + * a unique deterministic hash for the dependency group. Additionally, for explicitly configured redundancy + * groups, the non-unique dependency group name is also included on top of the composite keys. + * + * @return - Returns the composite key of the current dependency group. + */ +String DependencyGroup::GetCompositeKey() +{ + // This a copy of the CompositeKeyType definition but with the String type instead of Checkable* and TimePeriod*. + // This is because we need to produce a deterministic value from the composite key after each restart and that's + // not achievable using pointers. + using StringTuple = std::tuple; + std::vector compositeKeys; + { + std::lock_guard lock(m_Mutex); + for (auto& [compositeKey, _] : m_Members) { + auto [parent, tp, stateFilter, ignoreSoftStates] = compositeKey; + compositeKeys.emplace_back(parent->GetName(), tp ? tp->GetName() : "", stateFilter, ignoreSoftStates); + } + } + + // IMPORTANT: The order of the composite keys must be sorted to ensure the deterministic hash value. + std::sort(compositeKeys.begin(), compositeKeys.end()); + + Array::Ptr data(new Array{GetRedundancyGroupName()}); + for (auto& compositeKey : compositeKeys) { + auto [parent, tp, stateFilter, ignoreSoftStates] = compositeKey; + data->Add(std::move(parent)); + data->Add(std::move(tp)); + data->Add(stateFilter); + data->Add(ignoreSoftStates); + } + + return PackObject(data); +} diff --git a/lib/icinga/dependency.hpp b/lib/icinga/dependency.hpp index 32bd8e70d..afc5cab49 100644 --- a/lib/icinga/dependency.hpp +++ b/lib/icinga/dependency.hpp @@ -3,9 +3,17 @@ #ifndef DEPENDENCY_H #define DEPENDENCY_H +#include "base/shared-object.hpp" +#include "config/configitem.hpp" #include "icinga/i2-icinga.hpp" #include "icinga/dependency-ti.hpp" -#include "config/configitem.hpp" +#include "icinga/timeperiod.hpp" +#include +#include +#include +#include +#include +#include namespace icinga { @@ -60,6 +68,139 @@ private: static void BeforeOnAllConfigLoadedHandler(const ConfigItems& items); }; +/** + * A DependencyGroup represents a set of dependencies that are somehow related to each other. + * + * Specifically, a DependencyGroup is a container for Dependency objects of different Checkables that share the same + * child -> parent relationship config, thus forming a group of dependencies. All dependencies of a Checkable that + * have the same "redundancy_group" attribute value set are guaranteed to be part of the same DependencyGroup object, + * and another Checkable will join that group if and only if it has identical set of dependencies, that is, the same + * parent(s), same redundancy group name and all other dependency attributes required to form a composite key. + * + * More specifically, let's say we have a dependency graph like this: + * @verbatim + * PP1 PP2 + * /\ /\ + * || || + * ––––||–––––––––––––––||––––– + * P1 - ( "RG1" ) - P2 + * –––––––––––––––––––––––––––– + * /\ /\ + * || || + * C1 C2 + * @endverbatim + * The arrows represent a dependency relationship from bottom to top, i.e. both "C1" and "C2" depend on + * their "RG1" redundancy group, and "P1" and "P2" depend each on their respective parents (PP1, PP2 - no group). + * Now, as one can see, both "C1" and "C2" have identical dependencies, that is, they both depend on the same + * redundancy group "RG1" (these could e.g. be constructed through some Apply Rules). + * + * So, instead of having to maintain two separate copies of that graph, we can bring that imaginary redundancy group + * into reality by putting both "P1" and "P2" into an actual DependencyGroup object. However, we don't really put "P1" + * and "P2" objects into that group, but rather the actual Dependency objects of both child Checkables. Therefore, the + * group wouldn't just contain 2 dependencies, but 4 in total, i.e. 2 for each child Checkable (C1 -> {P1, P2} and + * C2 -> {P1, P2}). This way, both child Checkables can just refer to that very same DependencyGroup object. + * + * However, since not all dependencies are part of a redundancy group, we also have to consider the case where + * a Checkable has dependencies that are not part of any redundancy group, like P1 -> PP1. In such situations, + * each of the child Checkables (e.g. P1, P2) will have their own (sharable) DependencyGroup object just like for RGs. + * This allows us to keep the implementation simple and treat redundant and non-redundant dependencies in the same + * way, without having to introduce any special cases everywhere. So, in the end, we'd have 3 dependency groups in + * total, i.e. one for the redundancy group "RG1" (shared by C1 and C2), and two distinct groups for P1 and P2. + * + * @ingroup icinga + */ +class DependencyGroup final : public SharedObject +{ +public: + DECLARE_PTR_TYPEDEFS(DependencyGroup); + + /** + * Defines the key type of each dependency group members. + * + * This tuple consists of the dependency parent Checkable, the dependency time period (nullptr if not configured), + * the state filter, and the ignore soft states flag. Each of these values influences the availability of the + * dependency object, and thus used to group similar dependencies from different Checkables together. + */ + using CompositeKeyType = std::tuple; + + /** + * Represents the value type of each dependency group members. + * + * It stores the dependency objects of any given Checkable that produce the same composite key (CompositeKeyType). + * In other words, when looking at the dependency graph from the class description, the two dependency objects + * {C1, C2} -> P1 produce the same composite key, thus they are mapped to the same MemberValueType container with + * "C1" and "C2" as their keys respectively. Since Icinga 2 allows to construct different identical dependencies + * (duplicates), we're using a multimap instead of a simple map here. + */ + using MemberValueType = std::unordered_multimap; + using MembersMap = std::map; + + explicit DependencyGroup(String name); + + static void Register(const Dependency::Ptr& dependency); + static void Unregister(const Dependency::Ptr& dependency); + static size_t GetRegistrySize(); + + static CompositeKeyType MakeCompositeKeyFor(const Dependency::Ptr& dependency); + + /** + * Check whether the current dependency group represents an explicitly configured redundancy group. + * + * @return bool - Returns true if it's a redundancy group, false otherwise. + */ + inline bool IsRedundancyGroup() const + { + return !m_RedundancyGroupName.IsEmpty(); + } + + bool IsEmpty() const; + std::vector GetDependenciesForChild(const Checkable* child) const; + size_t GetDependenciesCount() const; + + void SetIcingaDBIdentifier(const String& identifier); + String GetIcingaDBIdentifier() const; + + const String& GetRedundancyGroupName() const; + String GetCompositeKey(); + +protected: + void AddDependency(const Dependency::Ptr& dependency); + void RemoveDependency(const Dependency::Ptr& dependency); + void CopyDependenciesTo(const DependencyGroup::Ptr& dest); + + static void RefreshRegistry(const Dependency::Ptr& dependency, bool unregister); + +private: + mutable std::mutex m_Mutex; + String m_IcingaDBIdentifier; + String m_RedundancyGroupName; + MembersMap m_Members; + + using RegistryType = boost::multi_index_container< + DependencyGroup*, // The type of the elements stored in the container. + boost::multi_index::indexed_by< + // This unique index allows to search/erase dependency groups by their composite key in an efficient manner. + boost::multi_index::hashed_unique< + boost::multi_index::mem_fun, + std::hash + >, + // This non-unique index allows to search for dependency groups by their name, and reduces the overall + // runtime complexity. Without this index, we would have to iterate over all elements to find the one + // with the desired members and since containers don't allow erasing elements while iterating, we would + // have to copy each of them to a temporary container, and then erase and reinsert them back to the original + // container. This produces way too much overhead, and slows down the startup time of Icinga 2 significantly. + boost::multi_index::hashed_non_unique< + boost::multi_index::const_mem_fun, + std::hash + > + > + >; + + // The global registry of dependency groups. + static std::mutex m_RegistryMutex; + static RegistryType m_Registry; +}; + } #endif /* DEPENDENCY_H */ From 1820955993980feab04cf0e1aaafec00aead15cb Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 5 Dec 2024 11:21:01 +0100 Subject: [PATCH 190/415] Add `DependencyGroup::GetState()` helper method --- lib/icinga/dependency-group.cpp | 43 +++++++++++++++++++++++++++++++++ lib/icinga/dependency.hpp | 8 ++++++ 2 files changed, 51 insertions(+) diff --git a/lib/icinga/dependency-group.cpp b/lib/icinga/dependency-group.cpp index 7d686fc0f..8b5aac92d 100644 --- a/lib/icinga/dependency-group.cpp +++ b/lib/icinga/dependency-group.cpp @@ -338,3 +338,46 @@ String DependencyGroup::GetCompositeKey() return PackObject(data); } + +/** + * Retrieve the state of the current dependency group. + * + * The state of the dependency group is determined based on the state of the parent Checkables and dependency objects + * of the group. A dependency group is considered unreachable when none of the parent Checkables is reachable. However, + * a dependency group may still be marked as failed even when it has reachable parent Checkables, but an unreachable + * group has always a failed state. + * + * @return - Returns the state of the current dependency group. + */ +DependencyGroup::State DependencyGroup::GetState(DependencyType dt, int rstack) const +{ + MembersMap members; + { + // We don't want to hold the mutex lock for the entire evaluation, thus we just need to operate on a copy. + std::lock_guard lock(m_Mutex); + members = m_Members; + } + + State state{false /* Reachable */, false /* OK */}; + for (auto& [_, children] : members) { + for (auto& [checkable, dependency] : children) { + state.Reachable = dependency->GetParent()->IsReachable(dt, rstack); + if (!state.Reachable && !IsRedundancyGroup()) { + return state; + } + + if (state.Reachable) { + state.OK = dependency->IsAvailable(dt); + // If this is a redundancy group, and we have found one functional path, that's enough and we can return. + // Likewise, if this is a non-redundant dependency group, and we have found one non-functional path, + // we have to mark the group as failed and return. + if (state.OK == IsRedundancyGroup()) { // OK && IsRedundancyGroup() || !OK && !IsRedundancyGroup() + return state; + } + } + break; // Move on to the next batch of group members (next composite key). + } + } + + return state; +} diff --git a/lib/icinga/dependency.hpp b/lib/icinga/dependency.hpp index afc5cab49..34667eec1 100644 --- a/lib/icinga/dependency.hpp +++ b/lib/icinga/dependency.hpp @@ -163,6 +163,14 @@ public: const String& GetRedundancyGroupName() const; String GetCompositeKey(); + struct State + { + bool Reachable; // Whether the dependency group is reachable. + bool OK; // Whether the dependency group is reachable and OK. + }; + + State GetState(DependencyType dt = DependencyState, int rstack = 0) const; + protected: void AddDependency(const Dependency::Ptr& dependency); void RemoveDependency(const Dependency::Ptr& dependency); From ff0dabe287978f187eb982f4cffdbdf0028734ac Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 4 Dec 2024 10:55:16 +0100 Subject: [PATCH 191/415] Checkable: Store dependencies grouped by their redundancy group --- lib/icinga/checkable-dependency.cpp | 19 +++++++++---------- lib/icinga/checkable.hpp | 4 +--- lib/icinga/dependency.cpp | 4 ++-- test/icinga-dependencies.cpp | 4 ++-- 4 files changed, 14 insertions(+), 17 deletions(-) diff --git a/lib/icinga/checkable-dependency.cpp b/lib/icinga/checkable-dependency.cpp index 8a838a7d5..a176a10ad 100644 --- a/lib/icinga/checkable-dependency.cpp +++ b/lib/icinga/checkable-dependency.cpp @@ -27,22 +27,21 @@ void Checkable::RemoveDependencyGroup(const DependencyGroup::Ptr& dependencyGrou m_DependencyGroups.erase(dependencyGroup); } -void Checkable::AddDependency(const Dependency::Ptr& dep) +std::vector Checkable::GetDependencyGroups() const { - std::unique_lock lock(m_DependencyMutex); - m_Dependencies.insert(dep); -} - -void Checkable::RemoveDependency(const Dependency::Ptr& dep) -{ - std::unique_lock lock(m_DependencyMutex); - m_Dependencies.erase(dep); + std::lock_guard lock(m_DependencyMutex); + return {m_DependencyGroups.begin(), m_DependencyGroups.end()}; } std::vector Checkable::GetDependencies() const { std::unique_lock lock(m_DependencyMutex); - return std::vector(m_Dependencies.begin(), m_Dependencies.end()); + std::vector dependencies; + for (const auto& dependencyGroup : m_DependencyGroups) { + auto tmpDependencies(dependencyGroup->GetDependenciesForChild(this)); + dependencies.insert(dependencies.end(), tmpDependencies.begin(), tmpDependencies.end()); + } + return dependencies; } void Checkable::AddReverseDependency(const Dependency::Ptr& dep) diff --git a/lib/icinga/checkable.hpp b/lib/icinga/checkable.hpp index 39cc8f570..04c12d4f9 100644 --- a/lib/icinga/checkable.hpp +++ b/lib/icinga/checkable.hpp @@ -187,8 +187,7 @@ public: /* Dependencies */ void AddDependencyGroup(const intrusive_ptr& dependencyGroup); void RemoveDependencyGroup(const intrusive_ptr& dependencyGroup); - void AddDependency(const intrusive_ptr& dep); - void RemoveDependency(const intrusive_ptr& dep); + std::vector> GetDependencyGroups() const; std::vector > GetDependencies() const; void AddReverseDependency(const intrusive_ptr& dep); @@ -250,7 +249,6 @@ private: /* Dependencies */ mutable std::mutex m_DependencyMutex; std::set> m_DependencyGroups; - std::set > m_Dependencies; std::set > m_ReverseDependencies; void GetAllChildrenInternal(std::set& seenChildren, int level = 0) const; diff --git a/lib/icinga/dependency.cpp b/lib/icinga/dependency.cpp index a9a7bf372..2f2482136 100644 --- a/lib/icinga/dependency.cpp +++ b/lib/icinga/dependency.cpp @@ -251,7 +251,7 @@ void Dependency::OnAllConfigLoaded() // InitChildParentReferences() has to be called before. VERIFY(m_Child && m_Parent); - m_Child->AddDependency(this); + DependencyGroup::Register(this); m_Parent->AddReverseDependency(this); } @@ -259,7 +259,7 @@ void Dependency::Stop(bool runtimeRemoved) { ObjectImpl::Stop(runtimeRemoved); - GetChild()->RemoveDependency(this); + DependencyGroup::Unregister(this); GetParent()->RemoveReverseDependency(this); } diff --git a/test/icinga-dependencies.cpp b/test/icinga-dependencies.cpp index 929b6ca0d..86735cdb9 100644 --- a/test/icinga-dependencies.cpp +++ b/test/icinga-dependencies.cpp @@ -54,7 +54,7 @@ BOOST_AUTO_TEST_CASE(multi_parent) dep1->SetStateFilter(StateFilterUp); // Reverse dependencies - childHost->AddDependency(dep1); + DependencyGroup::Register(dep1); parentHost1->AddReverseDependency(dep1); Dependency::Ptr dep2 = new Dependency(); @@ -64,7 +64,7 @@ BOOST_AUTO_TEST_CASE(multi_parent) dep2->SetStateFilter(StateFilterUp); // Reverse dependencies - childHost->AddDependency(dep2); + DependencyGroup::Register(dep2); parentHost2->AddReverseDependency(dep2); From 27f11a09559c9e1565be39f717ee3fd207194133 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 7 Feb 2025 11:09:34 +0100 Subject: [PATCH 192/415] Checkable: Introduce `HasAnyDependencies()` method --- lib/icinga/checkable-dependency.cpp | 6 ++++++ lib/icinga/checkable.hpp | 1 + 2 files changed, 7 insertions(+) diff --git a/lib/icinga/checkable-dependency.cpp b/lib/icinga/checkable-dependency.cpp index a176a10ad..afa1aca6d 100644 --- a/lib/icinga/checkable-dependency.cpp +++ b/lib/icinga/checkable-dependency.cpp @@ -44,6 +44,12 @@ std::vector Checkable::GetDependencies() const return dependencies; } +bool Checkable::HasAnyDependencies() const +{ + std::unique_lock lock(m_DependencyMutex); + return !m_DependencyGroups.empty() || !m_ReverseDependencies.empty(); +} + void Checkable::AddReverseDependency(const Dependency::Ptr& dep) { std::unique_lock lock(m_DependencyMutex); diff --git a/lib/icinga/checkable.hpp b/lib/icinga/checkable.hpp index 04c12d4f9..2b9014143 100644 --- a/lib/icinga/checkable.hpp +++ b/lib/icinga/checkable.hpp @@ -189,6 +189,7 @@ public: void RemoveDependencyGroup(const intrusive_ptr& dependencyGroup); std::vector> GetDependencyGroups() const; std::vector > GetDependencies() const; + bool HasAnyDependencies() const; void AddReverseDependency(const intrusive_ptr& dep); void RemoveReverseDependency(const intrusive_ptr& dep); From d094581b4b2105970f878dab35c4ef8d2646088d Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 16 Dec 2024 09:22:21 +0100 Subject: [PATCH 193/415] Checkable: Use redundancy groups state in `IsReachable` --- lib/icinga/checkable-dependency.cpp | 39 +++++------------------------ 1 file changed, 6 insertions(+), 33 deletions(-) diff --git a/lib/icinga/checkable-dependency.cpp b/lib/icinga/checkable-dependency.cpp index afa1aca6d..0904c09cf 100644 --- a/lib/icinga/checkable-dependency.cpp +++ b/lib/icinga/checkable-dependency.cpp @@ -77,11 +77,6 @@ bool Checkable::IsReachable(DependencyType dt, int rstack) const return false; } - for (const Checkable::Ptr& checkable : GetParents()) { - if (!checkable->IsReachable(dt, rstack + 1)) - return false; - } - /* implicit dependency on host if this is a service */ const auto *service = dynamic_cast(this); if (service && (dt == DependencyState || dt == DependencyNotification)) { @@ -92,38 +87,16 @@ bool Checkable::IsReachable(DependencyType dt, int rstack) const } } - auto deps = GetDependencies(); + for (auto& dependencyGroup : GetDependencyGroups()) { + if (auto state(dependencyGroup->GetState(dt, rstack + 1)); !state.Reachable || !state.OK) { + Log(LogDebug, "Checkable") + << "Dependency group '" << dependencyGroup->GetRedundancyGroupName() << "' have failed for checkable '" + << GetName() << "': Marking as unreachable."; - std::unordered_map violated; // key: redundancy group, value: nullptr if satisfied, violating dependency otherwise - - for (const Dependency::Ptr& dep : deps) { - std::string redundancy_group = dep->GetRedundancyGroup(); - - if (!dep->IsAvailable(dt)) { - if (redundancy_group.empty()) { - Log(LogDebug, "Checkable") - << "Non-redundant dependency '" << dep->GetName() << "' failed for checkable '" << GetName() << "': Marking as unreachable."; - - return false; - } - - // tentatively mark this dependency group as failed unless it is already marked; - // so it either passed before (don't overwrite) or already failed (so don't care) - // note that std::unordered_map::insert() will not overwrite an existing entry - violated.insert(std::make_pair(redundancy_group, dep)); - } else if (!redundancy_group.empty()) { - violated[redundancy_group] = nullptr; + return false; } } - auto violator = std::find_if(violated.begin(), violated.end(), [](auto& v) { return v.second != nullptr; }); - if (violator != violated.end()) { - Log(LogDebug, "Checkable") - << "All dependencies in redundancy group '" << violator->first << "' have failed for checkable '" << GetName() << "': Marking as unreachable."; - - return false; - } - return true; } From 2616c99891b4dc7f6a5b0747daf03f8bf1a82f97 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 16 Dec 2024 09:22:52 +0100 Subject: [PATCH 194/415] tests: Add unittests for the redundancy groups registry --- test/CMakeLists.txt | 3 + test/icinga-dependencies.cpp | 287 ++++++++++++++++++++++++++++++----- 2 files changed, 256 insertions(+), 34 deletions(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c4b1041dd..7857c1261 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -232,6 +232,9 @@ add_boost_test(base icinga_checkresult/service_flapping_notification icinga_checkresult/suppressed_notification icinga_dependencies/multi_parent + icinga_dependencies/default_redundancy_group_registration_unregistration + icinga_dependencies/simple_redundancy_group_registration_unregistration + icinga_dependencies/mixed_redundancy_group_registration_unregsitration icinga_notification/strings icinga_notification/state_filter icinga_notification/type_filter diff --git a/test/icinga-dependencies.cpp b/test/icinga-dependencies.cpp index 86735cdb9..e02385938 100644 --- a/test/icinga-dependencies.cpp +++ b/test/icinga-dependencies.cpp @@ -9,6 +9,52 @@ using namespace icinga; BOOST_AUTO_TEST_SUITE(icinga_dependencies) +static Host::Ptr CreateHost(const std::string& name) +{ + Host::Ptr host = new Host(); + host->SetName(name); + return host; +} + +static Dependency::Ptr CreateDependency(Checkable::Ptr parent, Checkable::Ptr child, const std::string& name) +{ + Dependency::Ptr dep = new Dependency(); + dep->SetParent(parent); + dep->SetChild(child); + dep->SetName(name + "!" + child->GetName()); + return dep; +} + +static void RegisterDependency(Dependency::Ptr dep, const std::string& redundancyGroup) +{ + dep->SetRedundancyGroup(redundancyGroup); + DependencyGroup::Register(dep); + dep->GetParent()->AddReverseDependency(dep); +} + +static void AssertCheckableRedundancyGroup(Checkable::Ptr checkable, int dependencyCount, int groupCount, int totalDependenciesCount) +{ + BOOST_CHECK_MESSAGE( + dependencyCount == checkable->GetDependencies().size(), + "Dependency count mismatch for '" << checkable->GetName() << "' - expected=" << dependencyCount << "; got=" + << checkable->GetDependencies().size() + ); + auto dependencyGroups(checkable->GetDependencyGroups()); + BOOST_CHECK_MESSAGE( + groupCount == dependencyGroups.size(), + "Dependency group count mismatch for '" << checkable->GetName() << "'" << " - expected=" << groupCount + << "; got=" << dependencyGroups.size() + ); + if (groupCount > 0) { + BOOST_REQUIRE_MESSAGE(1 <= dependencyGroups.size(), "Checkable '" << checkable->GetName() << "' should have at least one dependency group."); + BOOST_CHECK_MESSAGE( + totalDependenciesCount == dependencyGroups.begin()->get()->GetDependenciesCount(), + "Member count mismatch for '" << checkable->GetName() << "'" << " - expected=" << totalDependenciesCount + << "; got=" << dependencyGroups.begin()->get()->GetDependenciesCount() + ); + } +} + BOOST_AUTO_TEST_CASE(multi_parent) { /* One child host, two parent hosts. Simulate multi-parent dependencies. */ @@ -20,53 +66,28 @@ BOOST_AUTO_TEST_CASE(multi_parent) * - Parent objects need a CheckResult object * - Dependencies need a StateFilter */ - Host::Ptr parentHost1 = new Host(); - parentHost1->SetActive(true); - parentHost1->SetMaxCheckAttempts(1); - parentHost1->Activate(); - parentHost1->SetAuthority(true); + Host::Ptr parentHost1 = CreateHost("parentHost1"); parentHost1->SetStateRaw(ServiceCritical); parentHost1->SetStateType(StateTypeHard); parentHost1->SetLastCheckResult(new CheckResult()); - Host::Ptr parentHost2 = new Host(); - parentHost2->SetActive(true); - parentHost2->SetMaxCheckAttempts(1); - parentHost2->Activate(); - parentHost2->SetAuthority(true); + Host::Ptr parentHost2 = CreateHost("parentHost2"); parentHost2->SetStateRaw(ServiceOK); parentHost2->SetStateType(StateTypeHard); parentHost2->SetLastCheckResult(new CheckResult()); - Host::Ptr childHost = new Host(); - childHost->SetActive(true); - childHost->SetMaxCheckAttempts(1); - childHost->Activate(); - childHost->SetAuthority(true); + Host::Ptr childHost = CreateHost("childHost"); childHost->SetStateRaw(ServiceOK); childHost->SetStateType(StateTypeHard); /* Build the dependency tree. */ - Dependency::Ptr dep1 = new Dependency(); - - dep1->SetParent(parentHost1); - dep1->SetChild(childHost); + Dependency::Ptr dep1 (CreateDependency(parentHost1, childHost, "dep1")); dep1->SetStateFilter(StateFilterUp); + RegisterDependency(dep1, ""); - // Reverse dependencies - DependencyGroup::Register(dep1); - parentHost1->AddReverseDependency(dep1); - - Dependency::Ptr dep2 = new Dependency(); - - dep2->SetParent(parentHost2); - dep2->SetChild(childHost); + Dependency::Ptr dep2 (CreateDependency(parentHost2, childHost, "dep2")); dep2->SetStateFilter(StateFilterUp); - - // Reverse dependencies - DependencyGroup::Register(dep2); - parentHost2->AddReverseDependency(dep2); - + RegisterDependency(dep2, ""); /* Test the reachability from this point. * parentHost1 is DOWN, parentHost2 is UP. @@ -77,18 +98,42 @@ BOOST_AUTO_TEST_CASE(multi_parent) BOOST_CHECK(childHost->IsReachable() == false); + Dependency::Ptr duplicateDep (CreateDependency(parentHost1, childHost, "dep4")); + duplicateDep->SetIgnoreSoftStates(false, true); + RegisterDependency(duplicateDep, ""); + parentHost1->SetStateType(StateTypeSoft); + + // It should still be unreachable, due to the duplicated dependency object above with ignore_soft_states set to false. + BOOST_CHECK(childHost->IsReachable() == false); + parentHost1->SetStateType(StateTypeHard); + DependencyGroup::Unregister(duplicateDep); + /* The only DNS server is DOWN. * Expected result: childHost is unreachable. */ - dep1->SetRedundancyGroup("DNS"); + DependencyGroup::Unregister(dep1); // Remove the dep and re-add it with a configured redundancy group. + RegisterDependency(dep1, "DNS"); BOOST_CHECK(childHost->IsReachable() == false); /* 1/2 DNS servers is DOWN. * Expected result: childHost is reachable. */ - dep2->SetRedundancyGroup("DNS"); + DependencyGroup::Unregister(dep2); + RegisterDependency(dep2, "DNS"); BOOST_CHECK(childHost->IsReachable() == true); + auto grandParentHost(CreateHost("GrandParentHost")); + grandParentHost->SetLastCheckResult(new CheckResult()); + grandParentHost->SetStateRaw(ServiceCritical); + grandParentHost->SetStateType(StateTypeHard); + + Dependency::Ptr dep3 (CreateDependency(grandParentHost, parentHost1, "dep3")); + dep3->SetStateFilter(StateFilterUp); + RegisterDependency(dep3, ""); + // The grandparent is DOWN but the DNS redundancy group has to be still reachable. + BOOST_CHECK_EQUAL(true, childHost->IsReachable()); + DependencyGroup::Unregister(dep3); + /* Both DNS servers are DOWN. * Expected result: childHost is unreachable. */ @@ -98,4 +143,178 @@ BOOST_AUTO_TEST_CASE(multi_parent) BOOST_CHECK(childHost->IsReachable() == false); } +BOOST_AUTO_TEST_CASE(default_redundancy_group_registration_unregistration) +{ + Checkable::Ptr childHostC(CreateHost("C")); + Dependency::Ptr depCA(CreateDependency(CreateHost("A"), childHostC, "depCA")); + RegisterDependency(depCA, ""); + AssertCheckableRedundancyGroup(childHostC, 1, 1, 1); + BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize()); + + Dependency::Ptr depCB(CreateDependency(CreateHost("B"), childHostC, "depCB")); + RegisterDependency(depCB, ""); + AssertCheckableRedundancyGroup(childHostC, 2, 1, 2); + BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize()); + + Checkable::Ptr childHostD(CreateHost("D")); + Dependency::Ptr depDA(CreateDependency(depCA->GetParent(), childHostD, "depDA")); + RegisterDependency(depDA, ""); + AssertCheckableRedundancyGroup(childHostD, 1, 1, 1); + BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize()); + + Dependency::Ptr depDB(CreateDependency(depCB->GetParent(), childHostD, "depDB")); + RegisterDependency(depDB, ""); + AssertCheckableRedundancyGroup(childHostD, 2, 1, 4); + AssertCheckableRedundancyGroup(childHostC, 2, 1, 4); + BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize()); + + DependencyGroup::Unregister(depCA); + DependencyGroup::Unregister(depDA); + AssertCheckableRedundancyGroup(childHostC, 1, 1, 2); + AssertCheckableRedundancyGroup(childHostD, 1, 1, 2); + BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize()); + + DependencyGroup::Unregister(depCB); + DependencyGroup::Unregister(depDB); + AssertCheckableRedundancyGroup(childHostC, 0, 0, 0); + AssertCheckableRedundancyGroup(childHostD, 0, 0, 0); + BOOST_CHECK_EQUAL(0, DependencyGroup::GetRegistrySize()); +} + +BOOST_AUTO_TEST_CASE(simple_redundancy_group_registration_unregistration) +{ + Checkable::Ptr childHostC(CreateHost("childC")); + + Dependency::Ptr depCA(CreateDependency(CreateHost("A"), childHostC, "depCA")); + RegisterDependency(depCA, "redundant"); + AssertCheckableRedundancyGroup(childHostC, 1, 1, 1); + BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize()); + + Dependency::Ptr depCB(CreateDependency(CreateHost("B"), childHostC, "depCB")); + RegisterDependency(depCB, "redundant"); + AssertCheckableRedundancyGroup(childHostC, 2, 1, 2); + BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize()); + + Checkable::Ptr childHostD(CreateHost("childD")); + Dependency::Ptr depDA (CreateDependency(depCA->GetParent(), childHostD, "depDA")); + RegisterDependency(depDA, "redundant"); + AssertCheckableRedundancyGroup(childHostD, 1, 1, 1); + BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize()); + + Dependency::Ptr depDB(CreateDependency(depCB->GetParent(), childHostD, "depDB")); + RegisterDependency(depDB, "redundant"); + // Still 1 redundancy group, but there should be 4 dependencies now, i.e. 2 for each child Checkable. + AssertCheckableRedundancyGroup(childHostC, 2, 1, 4); + AssertCheckableRedundancyGroup(childHostD, 2, 1, 4); + BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize()); + + DependencyGroup::Unregister(depCA); + // After unregistering depCA, childHostC should have a new redundancy group with only depCB as dependency, and... + AssertCheckableRedundancyGroup(childHostC, 1, 1, 1); + // ...childHostD should still have the same redundancy group as before but also with only two dependencies. + AssertCheckableRedundancyGroup(childHostD, 2, 1, 2); + BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize()); + + DependencyGroup::Unregister(depDA); + // Nothing should have changed for childHostC, but childHostD should now have a fewer group dependency, i.e. + // both child hosts should have the same redundancy group with only depCB and depDB as dependency. + AssertCheckableRedundancyGroup(childHostC, 1, 1, 2); + AssertCheckableRedundancyGroup(childHostD, 1, 1, 2); + BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize()); + + DependencyGroup::Register(depDA); + DependencyGroup::Unregister(depDB); + // Nothing should have changed for childHostC, but both should now have a separate group with only depCB and depDA as dependency. + AssertCheckableRedundancyGroup(childHostC, 1, 1, 1); + AssertCheckableRedundancyGroup(childHostD, 1, 1, 1); + BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize()); + + DependencyGroup::Unregister(depCB); + DependencyGroup::Unregister(depDA); + AssertCheckableRedundancyGroup(childHostC, 0, 0, 0); + AssertCheckableRedundancyGroup(childHostD, 0, 0, 0); + BOOST_CHECK_EQUAL(0, DependencyGroup::GetRegistrySize()); +} + +BOOST_AUTO_TEST_CASE(mixed_redundancy_group_registration_unregsitration) +{ + Checkable::Ptr childHostC(CreateHost("childC")); + Dependency::Ptr depCA(CreateDependency(CreateHost("A"), childHostC, "depCA")); + RegisterDependency(depCA, "redundant"); + AssertCheckableRedundancyGroup(childHostC, 1, 1, 1); + BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize()); + + Checkable::Ptr childHostD(CreateHost("childD")); + Dependency::Ptr depDA(CreateDependency(depCA->GetParent(), childHostD, "depDA")); + RegisterDependency(depDA, "redundant"); + AssertCheckableRedundancyGroup(childHostD, 1, 1, 2); + BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize()); + + Dependency::Ptr depCB(CreateDependency(CreateHost("B"), childHostC, "depCB")); + RegisterDependency(depCB, "redundant"); + AssertCheckableRedundancyGroup(childHostC, 2, 1, 2); + AssertCheckableRedundancyGroup(childHostD, 1, 1, 1); + BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize()); + + Dependency::Ptr depDB(CreateDependency(depCB->GetParent(), childHostD, "depDB")); + RegisterDependency(depDB, "redundant"); + AssertCheckableRedundancyGroup(childHostC, 2, 1, 4); + AssertCheckableRedundancyGroup(childHostD, 2, 1, 4); + BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize()); + + Checkable::Ptr childHostE(CreateHost("childE")); + Dependency::Ptr depEA(CreateDependency(depCA->GetParent(), childHostE, "depEA")); + RegisterDependency(depEA, "redundant"); + AssertCheckableRedundancyGroup(childHostE, 1, 1, 1); + BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize()); + + Dependency::Ptr depEB(CreateDependency(depCB->GetParent(), childHostE, "depEB")); + RegisterDependency(depEB, "redundant"); + // All 3 hosts share the same group, and each host has 2 dependencies, thus 6 dependencies in total. + AssertCheckableRedundancyGroup(childHostC, 2, 1, 6); + AssertCheckableRedundancyGroup(childHostD, 2, 1, 6); + AssertCheckableRedundancyGroup(childHostE, 2, 1, 6); + BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize()); + + Dependency::Ptr depEZ(CreateDependency(CreateHost("Z"), childHostE, "depEZ")); + RegisterDependency(depEZ, "redundant"); + // Child host E should have a new redundancy group with 3 dependencies and the other two should still share the same group. + AssertCheckableRedundancyGroup(childHostC, 2, 1, 4); + AssertCheckableRedundancyGroup(childHostD, 2, 1, 4); + AssertCheckableRedundancyGroup(childHostE, 3, 1, 3); + BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize()); + + DependencyGroup::Unregister(depEA); + AssertCheckableRedundancyGroup(childHostC, 2, 1, 4); + AssertCheckableRedundancyGroup(childHostD, 2, 1, 4); + AssertCheckableRedundancyGroup(childHostE, 2, 1, 2); + BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize()); + + DependencyGroup::Register(depEA); // Re-register depEA and instead... + DependencyGroup::Unregister(depEZ); // ...unregister depEZ and check if all the hosts share the same group again. + // All 3 hosts share the same group again, and each host has 2 dependencies, thus 6 dependencies in total. + AssertCheckableRedundancyGroup(childHostC, 2, 1, 6); + AssertCheckableRedundancyGroup(childHostD, 2, 1, 6); + AssertCheckableRedundancyGroup(childHostE, 2, 1, 6); + BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize()); + + DependencyGroup::Unregister(depCA); + DependencyGroup::Unregister(depDB); + DependencyGroup::Unregister(depEB); + // Child host C has now a separate group with only depCB as dependency, and child hosts D and E share the same group. + AssertCheckableRedundancyGroup(childHostC, 1, 1, 1); + AssertCheckableRedundancyGroup(childHostD, 1, 1, 2); + AssertCheckableRedundancyGroup(childHostE, 1, 1, 2); + // Child host C has now a separate group with only depCB as member, and child hosts D and E share the same group. + BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize()); + + DependencyGroup::Unregister(depCB); + DependencyGroup::Unregister(depDA); + DependencyGroup::Unregister(depEA); + AssertCheckableRedundancyGroup(childHostC, 0, 0, 0); + AssertCheckableRedundancyGroup(childHostD, 0, 0, 0); + AssertCheckableRedundancyGroup(childHostE, 0, 0, 0); + BOOST_CHECK_EQUAL(0, DependencyGroup::GetRegistrySize()); +} + BOOST_AUTO_TEST_SUITE_END() From 4bfaefadfabf00eaa9ea3b9786aadbdb4c6840a8 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 16 Dec 2024 09:23:47 +0100 Subject: [PATCH 195/415] IcingaDB: Bump expected redis version to `6` --- lib/icingadb/icingadb-objects.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index cbdc48026..e087f429a 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -179,7 +179,7 @@ void IcingaDB::ConfigStaticInitialize() void IcingaDB::UpdateAllConfigObjects() { m_Rcon->Sync(); - m_Rcon->FireAndForgetQuery({"XADD", "icinga:schema", "MAXLEN", "1", "*", "version", "5"}, Prio::Heartbeat); + m_Rcon->FireAndForgetQuery({"XADD", "icinga:schema", "MAXLEN", "1", "*", "version", "6"}, Prio::Heartbeat); Log(LogInformation, "IcingaDB") << "Starting initial config/status dump"; double startTime = Utility::GetTime(); From fa63fda75b79631efe79003363946f7431d0dea2 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 14 Jan 2025 15:32:12 +0100 Subject: [PATCH 196/415] ApiListener: Simplify deferred SSL shutdown in `NewClientHandlerInternal()` --- lib/remote/apilistener.cpp | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 5863e52e8..772cd2df2 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -707,18 +707,14 @@ void ApiListener::NewClientHandlerInternal( return; } - bool willBeShutDown = false; + Defer shutdownSslConn ([&sslConn, &yc]() { + // Ignore the error, but do not throw an exception being swallowed at all cost. + // https://github.com/Icinga/icinga2/issues/7351 + boost::system::error_code ec; - Defer shutDownIfNeeded ([&sslConn, &willBeShutDown, &yc]() { - if (!willBeShutDown) { - // Ignore the error, but do not throw an exception being swallowed at all cost. - // https://github.com/Icinga/icinga2/issues/7351 - boost::system::error_code ec; - - // Using async_shutdown() instead of AsioTlsStream::GracefulDisconnect() as this whole function - // is already guarded by a timeout based on the connect timeout. - sslConn.async_shutdown(yc[ec]); - } + // Using async_shutdown() instead of AsioTlsStream::GracefulDisconnect() as this whole function + // is already guarded by a timeout based on the connect timeout. + sslConn.async_shutdown(yc[ec]); }); std::shared_ptr cert (sslConn.GetPeerCertificate()); @@ -831,7 +827,7 @@ void ApiListener::NewClientHandlerInternal( } } catch (const boost::system::system_error& systemError) { if (systemError.code() == boost::asio::error::operation_aborted) { - shutDownIfNeeded.Cancel(); + shutdownSslConn.Cancel(); } throw; @@ -867,8 +863,7 @@ void ApiListener::NewClientHandlerInternal( if (aclient) { aclient->Start(); - - willBeShutDown = true; + shutdownSslConn.Cancel(); } } else { Log(LogNotice, "ApiListener", "New HTTP client"); @@ -876,8 +871,7 @@ void ApiListener::NewClientHandlerInternal( HttpServerConnection::Ptr aclient = new HttpServerConnection(identity, verify_ok, client); AddHttpClient(aclient); aclient->Start(); - - willBeShutDown = true; + shutdownSslConn.Cancel(); } } From 3d761c029696b5b5dd28682cce27e470b1970909 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 5 Feb 2025 13:45:16 +0100 Subject: [PATCH 197/415] ApiActions: Remove child downtimes recursively Services downtimes scheduled via the `all_services` flag get already removed automatically when removing their parent downtimes (introduced with #8913). Now, this commit makes it possible to perform the same actions for all child downtimes, i.e. not only for those of service objects, but for all child objects represented in the dependency tree. --- lib/icinga/apiactions.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/icinga/apiactions.cpp b/lib/icinga/apiactions.cpp index 6e9fddd4d..3892de776 100644 --- a/lib/icinga/apiactions.cpp +++ b/lib/icinga/apiactions.cpp @@ -494,7 +494,7 @@ Dictionary::Ptr ApiActions::ScheduleDowntime(const ConfigObject::Ptr& object, << "Scheduling downtime for child object " << child->GetName(); Downtime::Ptr childDowntime = Downtime::AddDowntime(child, author, comment, startTime, endTime, - fixed, trigger, duration); + fixed, trigger, duration, String(), String(), downtimeName); String childDowntimeName = childDowntime->GetName(); Log(LogNotice, "ApiActions") From 66cc6a4d8aea822b12e7e34f13d0a70d75641aab Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 14 Mar 2025 12:30:22 +0100 Subject: [PATCH 198/415] ClusterEvents: Sync & process notification `notified_problem_users` --- lib/icinga/clusterevents.cpp | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/icinga/clusterevents.cpp b/lib/icinga/clusterevents.cpp index fe5167b78..b49d2071d 100644 --- a/lib/icinga/clusterevents.cpp +++ b/lib/icinga/clusterevents.cpp @@ -1281,6 +1281,7 @@ void ClusterEvents::NotificationSentToAllUsersHandler(const Notification::Ptr& n params->Set("notification_number", notification->GetNotificationNumber()); params->Set("last_problem_notification", notification->GetLastProblemNotification()); params->Set("no_more_notifications", notification->GetNoMoreNotifications()); + params->Set("notified_problem_users", notification->GetNotifiedProblemUsers()); Dictionary::Ptr message = new Dictionary(); message->Set("jsonrpc", "2.0"); @@ -1373,12 +1374,16 @@ Value ClusterEvents::NotificationSentToAllUsersAPIHandler(const MessageOrigin::P notification->SetLastProblemNotification(params->Get("last_problem_notification")); notification->SetNoMoreNotifications(params->Get("no_more_notifications")); - ArrayData notifiedProblemUsers; - for (const User::Ptr& user : users) { - notifiedProblemUsers.push_back(user->GetName()); - } + if (params->Contains("notified_problem_users")) { + notification->SetNotifiedProblemUsers(params->Get("notified_problem_users")); + } else { + ArrayData notifiedProblemUsers; + for (const User::Ptr& user : users) { + notifiedProblemUsers.push_back(user->GetName()); + } - notification->SetNotifiedProblemUsers(new Array(std::move(notifiedProblemUsers))); + notification->SetNotifiedProblemUsers(new Array(std::move(notifiedProblemUsers))); + } Checkable::OnNotificationSentToAllUsers(notification, checkable, users, type, cr, author, text, origin); From 55885e0cd9bf244aa491fc970f7461a2bca6b8d8 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 17 Mar 2025 10:09:37 +0100 Subject: [PATCH 199/415] Enable dependabot for GitHub Actions --- .github/dependabot.yml | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..1a7ee1277 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,7 @@ +version: 2 +updates: + +- package-ecosystem: github-actions + directory: / + schedule: + interval: daily From 291ee17ca82c83602aaca2f208b9d3303a98d05c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Mon, 17 Mar 2025 12:34:00 +0100 Subject: [PATCH 200/415] GHA: AUTHORS: ignore dependabot[bot] --- .github/workflows/authors-file.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/authors-file.yml b/.github/workflows/authors-file.yml index 9e13ab686..2e5ac25ee 100644 --- a/.github/workflows/authors-file.yml +++ b/.github/workflows/authors-file.yml @@ -21,7 +21,7 @@ jobs: git add AUTHORS git log --format='format:%aN <%aE>' "$( git merge-base HEAD^1 HEAD^2 - )..HEAD^2" >> AUTHORS + )..HEAD^2" | grep -vEe '^dependabot\[bot] ' >> AUTHORS sort -uo AUTHORS AUTHORS git diff AUTHORS >> AUTHORS.diff From d387f0cd18fd5167290dc3ebe37e03e51c95dedc Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 20 Dec 2023 13:14:56 +0100 Subject: [PATCH 201/415] GHA: also build on Alpine to test LibreSSL which is used on OpenBSD --- .github/workflows/alpine-bash.Dockerfile | 2 ++ .github/workflows/linux.bash | 8 +++++++- .github/workflows/linux.yml | 9 ++++++++- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/alpine-bash.Dockerfile diff --git a/.github/workflows/alpine-bash.Dockerfile b/.github/workflows/alpine-bash.Dockerfile new file mode 100644 index 000000000..26f6a5f9e --- /dev/null +++ b/.github/workflows/alpine-bash.Dockerfile @@ -0,0 +1,2 @@ +FROM alpine:3 +RUN ["apk", "--no-cache", "add", "bash"] diff --git a/.github/workflows/linux.bash b/.github/workflows/linux.bash index a94f7d1b7..cb6ccb920 100755 --- a/.github/workflows/linux.bash +++ b/.github/workflows/linux.bash @@ -1,12 +1,18 @@ #!/bin/bash set -exo pipefail -export PATH="/usr/lib/ccache:/usr/lib64/ccache:$PATH" +export PATH="/usr/lib/ccache/bin:/usr/lib/ccache:/usr/lib64/ccache:$PATH" export CCACHE_DIR=/icinga2/ccache export CTEST_OUTPUT_ON_FAILURE=1 CMAKE_OPTS='' case "$DISTRO" in + alpine:*) + apk add bison {boost,libressl}-dev ccache cmake flex g++ ninja-build tzdata + ln -vs /usr/lib/ninja-build/bin/ninja /usr/local/bin/ninja + CMAKE_OPTS="-DUSE_SYSTEMD=OFF $(echo -DICINGA2_WITH_{MYSQL,PGSQL,COMPAT,LIVESTATUS,PERFDATA,ICINGADB}=OFF)" + ;; + amazonlinux:2) amazon-linux-extras install -y epel yum install -y bison ccache cmake3 gcc-c++ flex ninja-build \ diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 829161cce..ae8de5527 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -21,6 +21,7 @@ jobs: max-parallel: 2 matrix: distro: + - alpine:bash # LibreSSL, used on OpenBSD - amazonlinux:2 - amazonlinux:2023 @@ -68,7 +69,13 @@ jobs: path: ccache key: ccache/${{ matrix.distro }} - - name: Build + - name: Build Docker image + if: "matrix.distro == 'alpine:bash'" + run: >- + docker build --file .github/workflows/alpine-bash.Dockerfile + --tag alpine:bash `mktemp -d` + + - name: Build Icinga run: >- docker run --rm -v "$(pwd):/icinga2" -e DISTRO=${{ matrix.distro }} --platform ${{ matrix.platform }} ${{ matrix.distro }} /icinga2/.github/workflows/linux.bash From b521a9742ea2aa1f048756c4a020255e297a39c3 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Thu, 9 Jan 2025 14:58:27 +0100 Subject: [PATCH 202/415] GHA: Fix Alpine LibreSSL First, the icinga_legacytimeperiod/dst test was excluded, as it fails on Alpine most likely due to some differences between musl and glibc. After some debugging, I disabled the test as the Alpine packages does. More build dependencies were added from the Alpine package, allowing to only disable MySQL and PostgreSQL support as these libraries have fixed dependencies on OpenSSL, conflicting with LibreSSL. In addition, I have added comments where I was first puzzled. --- .github/workflows/alpine-bash.Dockerfile | 6 ++++++ .github/workflows/linux.bash | 14 ++++++++++++-- .github/workflows/linux.yml | 7 +++++-- 3 files changed, 23 insertions(+), 4 deletions(-) diff --git a/.github/workflows/alpine-bash.Dockerfile b/.github/workflows/alpine-bash.Dockerfile index 26f6a5f9e..9119f5744 100644 --- a/.github/workflows/alpine-bash.Dockerfile +++ b/.github/workflows/alpine-bash.Dockerfile @@ -1,2 +1,8 @@ +# This Dockerfile is used in the linux job for Alpine Linux. +# +# As the linux.bash script is, in fact, a bash script and Alpine does not ship +# a bash by default, the "alpine:bash" container will be built using this +# Dockerfile in the GitHub Action. + FROM alpine:3 RUN ["apk", "--no-cache", "add", "bash"] diff --git a/.github/workflows/linux.bash b/.github/workflows/linux.bash index cb6ccb920..3b05429d7 100755 --- a/.github/workflows/linux.bash +++ b/.github/workflows/linux.bash @@ -8,9 +8,19 @@ CMAKE_OPTS='' case "$DISTRO" in alpine:*) - apk add bison {boost,libressl}-dev ccache cmake flex g++ ninja-build tzdata + # Packages inspired by the Alpine package, just + # - LibreSSL instead of OpenSSL 3 and + # - no MariaDB or libpq as they depend on OpenSSL. + # https://gitlab.alpinelinux.org/alpine/aports/-/blob/master/community/icinga2/APKBUILD + apk add bison boost-dev ccache cmake flex g++ libedit-dev libressl-dev ninja-build tzdata ln -vs /usr/lib/ninja-build/bin/ninja /usr/local/bin/ninja - CMAKE_OPTS="-DUSE_SYSTEMD=OFF $(echo -DICINGA2_WITH_{MYSQL,PGSQL,COMPAT,LIVESTATUS,PERFDATA,ICINGADB}=OFF)" + + CMAKE_OPTS="-DUSE_SYSTEMD=OFF -DICINGA2_WITH_MYSQL=OFF -DICINGA2_WITH_PGSQL=OFF" + + # This test fails due to some glibc/musl mismatch regarding timezone PST/PDT. + # - https://www.openwall.com/lists/musl/2024/03/05/2 + # - https://gitlab.alpinelinux.org/alpine/aports/-/blob/b3ea02e2251451f9511086e1970f21eb640097f7/community/icinga2/disable-failing-tests.patch + sed -i '/icinga_legacytimeperiod\/dst$/d' /icinga2/test/CMakeLists.txt ;; amazonlinux:2) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index ae8de5527..7410c6894 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -21,7 +21,10 @@ jobs: max-parallel: 2 matrix: distro: - - alpine:bash # LibreSSL, used on OpenBSD + # Alpine Linux to build Icinga 2 with LibreSSL, OpenBSD's default. + # The "alpine:bash" image will be built below based on "alpine:3". + - alpine:bash + - amazonlinux:2 - amazonlinux:2023 @@ -69,7 +72,7 @@ jobs: path: ccache key: ccache/${{ matrix.distro }} - - name: Build Docker image + - name: Build Alpine Docker Image if: "matrix.distro == 'alpine:bash'" run: >- docker build --file .github/workflows/alpine-bash.Dockerfile From cce03c59039442564411f6f26367342a00b612c7 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 8 Nov 2024 11:33:57 +0100 Subject: [PATCH 203/415] Remove unused ApiAction::Unregister() --- lib/remote/apiaction.cpp | 5 ----- lib/remote/apiaction.hpp | 1 - 2 files changed, 6 deletions(-) diff --git a/lib/remote/apiaction.cpp b/lib/remote/apiaction.cpp index 4da91f0d5..ccde28190 100644 --- a/lib/remote/apiaction.cpp +++ b/lib/remote/apiaction.cpp @@ -29,11 +29,6 @@ void ApiAction::Register(const String& name, const ApiAction::Ptr& action) ApiActionRegistry::GetInstance()->Register(name, action); } -void ApiAction::Unregister(const String& name) -{ - ApiActionRegistry::GetInstance()->Unregister(name); -} - ApiActionRegistry *ApiActionRegistry::GetInstance() { return Singleton::GetInstance(); diff --git a/lib/remote/apiaction.hpp b/lib/remote/apiaction.hpp index f2719c1bb..2bb98b1b6 100644 --- a/lib/remote/apiaction.hpp +++ b/lib/remote/apiaction.hpp @@ -34,7 +34,6 @@ public: static ApiAction::Ptr GetByName(const String& name); static void Register(const String& name, const ApiAction::Ptr& action); - static void Unregister(const String& name); private: std::vector m_Types; From 41f61ccba4ce8c60cb5390f99658ad984c5bbc1e Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 8 Nov 2024 11:35:35 +0100 Subject: [PATCH 204/415] Remove unused ApiFunction::Unregister() --- lib/remote/apifunction.cpp | 5 ----- lib/remote/apifunction.hpp | 1 - 2 files changed, 6 deletions(-) diff --git a/lib/remote/apifunction.cpp b/lib/remote/apifunction.cpp index 5b855cc43..89e1d8734 100644 --- a/lib/remote/apifunction.cpp +++ b/lib/remote/apifunction.cpp @@ -24,11 +24,6 @@ void ApiFunction::Register(const String& name, const ApiFunction::Ptr& function) ApiFunctionRegistry::GetInstance()->Register(name, function); } -void ApiFunction::Unregister(const String& name) -{ - ApiFunctionRegistry::GetInstance()->Unregister(name); -} - ApiFunctionRegistry *ApiFunctionRegistry::GetInstance() { return Singleton::GetInstance(); diff --git a/lib/remote/apifunction.hpp b/lib/remote/apifunction.hpp index e6113204d..5a99db518 100644 --- a/lib/remote/apifunction.hpp +++ b/lib/remote/apifunction.hpp @@ -31,7 +31,6 @@ public: static ApiFunction::Ptr GetByName(const String& name); static void Register(const String& name, const ApiFunction::Ptr& function); - static void Unregister(const String& name); private: Callback m_Callback; From d19c0637eeedfb2eb62b6a3b6a20bcf0df2e2e9c Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 8 Nov 2024 11:36:49 +0100 Subject: [PATCH 205/415] Remove unused EventQueue::UnregisterIfUnused() --- lib/remote/eventqueue.cpp | 8 -------- lib/remote/eventqueue.hpp | 1 - 2 files changed, 9 deletions(-) diff --git a/lib/remote/eventqueue.cpp b/lib/remote/eventqueue.cpp index d79b61589..7b960daa4 100644 --- a/lib/remote/eventqueue.cpp +++ b/lib/remote/eventqueue.cpp @@ -71,14 +71,6 @@ void EventQueue::RemoveClient(void *client) m_Events.erase(client); } -void EventQueue::UnregisterIfUnused(const String& name, const EventQueue::Ptr& queue) -{ - std::unique_lock lock(queue->m_Mutex); - - if (queue->m_Events.empty()) - Unregister(name); -} - void EventQueue::SetTypes(const std::set& types) { std::unique_lock lock(m_Mutex); diff --git a/lib/remote/eventqueue.hpp b/lib/remote/eventqueue.hpp index 32bd34a7a..986ad5844 100644 --- a/lib/remote/eventqueue.hpp +++ b/lib/remote/eventqueue.hpp @@ -38,7 +38,6 @@ public: Dictionary::Ptr WaitForEvent(void *client, double timeout = 5); static std::vector GetQueuesForType(const String& type); - static void UnregisterIfUnused(const String& name, const EventQueue::Ptr& queue); static EventQueue::Ptr GetByName(const String& name); static void Register(const String& name, const EventQueue::Ptr& function); From 402a6bbf40c51b408d0abc6c1c0384fd25fbb8d6 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 8 Nov 2024 11:37:44 +0100 Subject: [PATCH 206/415] Remove unused EventQueue::Unregister() --- lib/remote/eventqueue.cpp | 5 ----- lib/remote/eventqueue.hpp | 1 - 2 files changed, 6 deletions(-) diff --git a/lib/remote/eventqueue.cpp b/lib/remote/eventqueue.cpp index 7b960daa4..4705d4050 100644 --- a/lib/remote/eventqueue.cpp +++ b/lib/remote/eventqueue.cpp @@ -127,11 +127,6 @@ void EventQueue::Register(const String& name, const EventQueue::Ptr& function) EventQueueRegistry::GetInstance()->Register(name, function); } -void EventQueue::Unregister(const String& name) -{ - EventQueueRegistry::GetInstance()->Unregister(name); -} - EventQueueRegistry *EventQueueRegistry::GetInstance() { return Singleton::GetInstance(); diff --git a/lib/remote/eventqueue.hpp b/lib/remote/eventqueue.hpp index 986ad5844..833714f9d 100644 --- a/lib/remote/eventqueue.hpp +++ b/lib/remote/eventqueue.hpp @@ -41,7 +41,6 @@ public: static EventQueue::Ptr GetByName(const String& name); static void Register(const String& name, const EventQueue::Ptr& function); - static void Unregister(const String& name); private: String m_Name; From 07b274ec45f3db795b5324f29a53924d5200f16f Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 8 Nov 2024 11:38:19 +0100 Subject: [PATCH 207/415] Remove unused Registry#Unregister() --- lib/base/registry.hpp | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/lib/base/registry.hpp b/lib/base/registry.hpp index c13f7e1b0..55462e356 100644 --- a/lib/base/registry.hpp +++ b/lib/base/registry.hpp @@ -40,19 +40,6 @@ public: RegisterInternal(name, item, lock); } - void Unregister(const String& name) - { - size_t erased; - - { - std::unique_lock lock(m_Mutex); - erased = m_Items.erase(name); - } - - if (erased > 0) - OnUnregistered(name); - } - void Clear() { typename Registry::ItemMap items; From 4d7361527cb684b56caaf43ff2c5e6f9c2b64064 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 8 Nov 2024 11:42:21 +0100 Subject: [PATCH 208/415] Remove unused Registry#RegisterIfNew() --- lib/base/registry.hpp | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/base/registry.hpp b/lib/base/registry.hpp index 55462e356..874259764 100644 --- a/lib/base/registry.hpp +++ b/lib/base/registry.hpp @@ -23,16 +23,6 @@ class Registry public: typedef std::map ItemMap; - void RegisterIfNew(const String& name, const T& item) - { - std::unique_lock lock(m_Mutex); - - if (m_Items.find(name) != m_Items.end()) - return; - - RegisterInternal(name, item, lock); - } - void Register(const String& name, const T& item) { std::unique_lock lock(m_Mutex); From a9e9e14fce90f99f8b40df1f3897b73d408a212e Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 8 Nov 2024 11:43:31 +0100 Subject: [PATCH 209/415] Remove unused Registry#Clear() --- lib/base/registry.hpp | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/lib/base/registry.hpp b/lib/base/registry.hpp index 874259764..5a8ac26f7 100644 --- a/lib/base/registry.hpp +++ b/lib/base/registry.hpp @@ -30,25 +30,6 @@ public: RegisterInternal(name, item, lock); } - void Clear() - { - typename Registry::ItemMap items; - - { - std::unique_lock lock(m_Mutex); - items = m_Items; - } - - for (const auto& kv : items) { - OnUnregistered(kv.first); - } - - { - std::unique_lock lock(m_Mutex); - m_Items.clear(); - } - } - T GetItem(const String& name) const { std::unique_lock lock(m_Mutex); From 63926c6e0d8a44c39aab1ced7ca72982083c6493 Mon Sep 17 00:00:00 2001 From: Richard Mortimer Date: Tue, 18 Mar 2025 10:29:00 +0000 Subject: [PATCH 210/415] Process: Clean up process table entry even when `kill(2)` fails with `ESRCH` (#10375) * Icinga daemon leaves zombie processes on very busy system On a very heavily loaded system the process group kill can be delayed until after the regular TERM signal has caused the process to exit. In this situation the waitpid call is valid and reaps the zombie process that would otherwise be left behind. * Update AUTHORS file --- AUTHORS | 1 + lib/base/process.cpp | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 73ab10637..4207f57f8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -242,6 +242,7 @@ pv2b Ralph Breier Reto Zeder Ricardo Bartels +Richard Mortimer Rinck H. Sonnenberg Robert Lindgren Robert Scheck diff --git a/lib/base/process.cpp b/lib/base/process.cpp index a076dcf32..23b735352 100644 --- a/lib/base/process.cpp +++ b/lib/base/process.cpp @@ -1087,7 +1087,9 @@ bool Process::DoEvents() Log(LogWarning, "Process") << "Couldn't kill the process group " << m_PID << " (" << PrettyPrintArguments(m_Arguments) << "): [errno " << error << "] " << strerror(error); - could_not_kill = true; + if (error != ESRCH) { + could_not_kill = true; + } } #endif /* _WIN32 */ From c6466ee0eaea6e76c9a1800aae6719a8d091d167 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 4 Dec 2024 11:00:26 +0100 Subject: [PATCH 211/415] IcingaDB: Dump checkables dependencies config to redis correctly --- lib/icingadb/icingadb-objects.cpp | 376 +++++++++++++++--------------- lib/icingadb/icingadb.cpp | 2 - lib/icingadb/icingadb.hpp | 17 +- 3 files changed, 199 insertions(+), 196 deletions(-) diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index e087f429a..33cf973ce 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -62,7 +62,6 @@ std::vector IcingaDB::GetTypes() // Then sync them for similar reasons. Downtime::TypeInstance, Comment::TypeInstance, - Dependency::TypeInstance, HostGroup::TypeInstance, ServiceGroup::TypeInstance, @@ -208,10 +207,17 @@ void IcingaDB::UpdateAllConfigObjects() m_Rcon->FireAndForgetQuery({"XADD", "icinga:dump", "MAXLEN", "1", "*", "key", "*", "state", "wip"}, Prio::Config); const std::vector globalKeys = { - m_PrefixConfigObject + "customvar", - m_PrefixConfigObject + "action:url", - m_PrefixConfigObject + "notes:url", - m_PrefixConfigObject + "icon:image", + m_PrefixConfigObject + "customvar", + m_PrefixConfigObject + "action:url", + m_PrefixConfigObject + "notes:url", + m_PrefixConfigObject + "icon:image", + + // These keys aren't tied to a specific Checkable type but apply to both "Host" and "Service" types, + // and as such we've to make sure to clear them before we actually start dumping the actual objects. + // This allows us to wait on both types to be dumped before we send a config dump done signal for those keys. + m_PrefixConfigObject + "dependency:node", + m_PrefixConfigObject + "dependency:edge", + m_PrefixConfigObject + "redundancygroup", }; DeleteKeys(m_Rcon, globalKeys, Prio::Config); DeleteKeys(m_Rcon, {"icinga:nextupdate:host", "icinga:nextupdate:service"}, Prio::Config); @@ -222,6 +228,7 @@ void IcingaDB::UpdateAllConfigObjects() m_DumpedGlobals.ActionUrl.Reset(); m_DumpedGlobals.NotesUrl.Reset(); m_DumpedGlobals.IconImage.Reset(); + m_DumpedGlobals.DependencyGroup.Reset(); }); upq.ParallelFor(types, false, [this](const Type::Ptr& type) { @@ -793,140 +800,11 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S } } + InsertCheckableDependencies(checkable, hMSets, runtimeUpdate ? &runtimeUpdates : nullptr); + return; } - if (type == Dependency::TypeInstance) { - auto& dependencyNodes (hMSets[m_PrefixConfigObject + "dependency:node"]); - auto& dependencyEdges (hMSets[m_PrefixConfigObject + "dependency:edge"]); - auto& redundancyGroups (hMSets[m_PrefixConfigObject + "redundancygroup"]); - - Dependency::Ptr dependency = static_pointer_cast(object); - - Host::Ptr parentHost, childHost; - Service::Ptr parentService, childService; - tie(parentHost, parentService) = GetHostService(dependency->GetParent()); - tie(childHost, childService) = GetHostService(dependency->GetChild()); - String redundancyGroup = dependency->GetRedundancyGroup(); - - String redundancyGroupId, dependencyNodeParentId, dependencyNodeChildId, dependencyNodeReduId; - - Dictionary::Ptr parentNodeData, childNodeData; - - if (parentService) { - dependencyNodeParentId = HashValue(new Array({ - m_EnvironmentId, - GetObjectIdentifier(parentHost), - GetObjectIdentifier(parentService)})); - parentNodeData = new Dictionary({ - {"environment_id", m_EnvironmentId}, - {"host_id", GetObjectIdentifier(parentHost)}, - {"service_id", GetObjectIdentifier(parentService)}}); - - m_CheckablesToDependencies->Set(GetObjectIdentifier(parentService), dependency); - } else { - dependencyNodeParentId = HashValue(new Array({ - m_EnvironmentId, - GetObjectIdentifier(parentHost)})); - parentNodeData = new Dictionary({ - {"environment_id", m_EnvironmentId}, - {"host_id", GetObjectIdentifier(parentHost)}}); - - m_CheckablesToDependencies->Set(GetObjectIdentifier(parentHost), dependency); - } - - if (childService) { - dependencyNodeChildId = HashValue(new Array({ - m_EnvironmentId, - GetObjectIdentifier(childHost), - GetObjectIdentifier(childService)})); - childNodeData = new Dictionary({ - {"environment_id", m_EnvironmentId}, - {"host_id", GetObjectIdentifier(childHost)}, - {"service_id", GetObjectIdentifier(childService)}}); - - m_CheckablesToDependencies->Set(GetObjectIdentifier(childService), dependency); - } else { - dependencyNodeChildId = HashValue(new Array({ - m_EnvironmentId, - GetObjectIdentifier(childHost)})); - childNodeData = new Dictionary({ - {"environment_id", m_EnvironmentId}, - {"host_id", GetObjectIdentifier(childHost)}}); - - m_CheckablesToDependencies->Set(GetObjectIdentifier(childHost), dependency); - } - - dependencyNodes.emplace_back(dependencyNodeParentId); - dependencyNodes.emplace_back(JsonEncode(parentNodeData)); - dependencyNodes.emplace_back(dependencyNodeChildId); - dependencyNodes.emplace_back(JsonEncode(childNodeData)); - - if (runtimeUpdate) { - AddObjectDataToRuntimeUpdates(runtimeUpdates, dependencyNodeParentId, m_PrefixConfigObject + "dependency:node", parentNodeData); - AddObjectDataToRuntimeUpdates(runtimeUpdates, dependencyNodeChildId, m_PrefixConfigObject + "dependency:node", childNodeData); - } - - if (!redundancyGroup.IsEmpty()) { - /* TODO: name should be suffixed with names of all children. - * however, at this point I don't have this information, - * only the direct neighbors. - */ - redundancyGroupId = HashValue(new Array({m_EnvironmentId, redundancyGroup, dependencyNodeChildId})); - dependencyNodeReduId = redundancyGroupId; - - redundancyGroups.emplace_back(redundancyGroupId); - Dictionary::Ptr groupData = new Dictionary({ - {"environment_id", m_EnvironmentId}, - {"name", redundancyGroupId}, - {"display_name", redundancyGroup}}); - redundancyGroups.emplace_back(JsonEncode(groupData)); - - dependencyNodes.emplace_back(dependencyNodeReduId); - Dictionary::Ptr reduNodeData = new Dictionary({ - {"environment_id", m_EnvironmentId}, - {"redundancy_group_id", redundancyGroupId}}); - dependencyNodes.emplace_back(JsonEncode(reduNodeData)); - - String edgeInId = HashValue(new Array({m_EnvironmentId, dependencyNodeChildId, dependencyNodeReduId})); - dependencyEdges.emplace_back(edgeInId); - Dictionary::Ptr edgeInData = new Dictionary({ - {"environment_id", m_EnvironmentId}, - {"from_node_id", dependencyNodeChildId}, - {"to_node_id", dependencyNodeReduId}}); - dependencyEdges.emplace_back(JsonEncode(edgeInData)); - - String edgeOutId = HashValue(new Array({m_EnvironmentId, dependencyNodeReduId, dependencyNodeParentId})); - dependencyEdges.emplace_back(edgeOutId); - Dictionary::Ptr edgeOutData = new Dictionary({ - {"environment_id", m_EnvironmentId}, - {"from_node_id", dependencyNodeReduId}, - {"to_node_id", dependencyNodeParentId}, - {"dependency_id", GetObjectIdentifier(dependency)}}); - dependencyEdges.emplace_back(JsonEncode(edgeOutData)); - - if (runtimeUpdate) { - AddObjectDataToRuntimeUpdates(runtimeUpdates, redundancyGroupId, m_PrefixConfigObject + "redundancygroup", groupData); - AddObjectDataToRuntimeUpdates(runtimeUpdates, dependencyNodeReduId, m_PrefixConfigObject + "dependency:node", reduNodeData); - AddObjectDataToRuntimeUpdates(runtimeUpdates, edgeInId, m_PrefixConfigObject + "dependency:edge", edgeInData); - AddObjectDataToRuntimeUpdates(runtimeUpdates, edgeOutId, m_PrefixConfigObject + "dependency:edge", edgeOutData); - } - } else { - String edgeId = HashValue(new Array({m_EnvironmentId, dependencyNodeChildId, dependencyNodeParentId})); - dependencyEdges.emplace_back(edgeId); - Dictionary::Ptr edgeData = new Dictionary({ - {"environment_id", m_EnvironmentId}, - {"from_node_id", dependencyNodeChildId}, - {"to_node_id", dependencyNodeParentId}, - {"dependency_id", GetObjectIdentifier(dependency)}}); - dependencyEdges.emplace_back(JsonEncode(edgeData)); - - if (runtimeUpdate) { - AddObjectDataToRuntimeUpdates(runtimeUpdates, edgeId, m_PrefixConfigObject + "dependency:edge", edgeData); - } - } - } - if (type == TimePeriod::TypeInstance) { TimePeriod::Ptr timeperiod = static_pointer_cast(object); @@ -1257,44 +1135,156 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S } } -void IcingaDB::UpdateDependencyState(const Dependency::Ptr& dependency) +/** + * Inserts the dependency data for a Checkable object into the given Redis HMSETs and runtime updates. + * + * This function is responsible for serializing the in memory representation Checkable dependencies into + * Redis HMSETs and runtime updates (if any) according to the Icinga DB schema. The serialized data consists + * of the following Redis HMSETs: + * - RedisKey::DependencyNode: Contains dependency node data representing each host, service, and redundancy group + * in the dependency graph. + * - RedisKey::DependencyEdge: Dependency edge information representing all connections between the nodes. + * - RedisKey::RedundancyGroup: Redundancy group data representing all redundancy groups in the graph. + * + * For initial dumps, it shouldn't be necessary to set the `runtimeUpdates` parameter. + * + * @param checkable The checkable object to extract dependencies from. + * @param hMSets The map of Redis HMSETs to insert the dependency data into. + * @param runtimeUpdates If set, runtime updates are additionally added to this vector. + */ +void IcingaDB::InsertCheckableDependencies( + const Checkable::Ptr& checkable, + std::map& hMSets, + std::vector* runtimeUpdates +) { - if (!m_Rcon || !m_Rcon->IsConnected()) { + // Only generate a dependency node event if the Checkable is actually part of some dependency graph. + // That's, it either depends on other Checkables or others depend on it, and in both cases, we have + // to at least generate a dependency node entry for it. + if (!checkable->HasAnyDependencies()) { return; } - auto& redundancyGroupStates (hMSets[m_PrefixConfigObject + "redundancygroup:state"]); + // First and foremost, generate a dependency node entry for the provided Checkable object and insert it into + // the HMSETs map and if set, the `runtimeUpdates` vector. + auto [host, service] = GetHostService(checkable); + auto checkableId(GetObjectIdentifier(checkable)); + { + Dictionary::Ptr data(new Dictionary{{"environment_id", m_EnvironmentId}, {"host_id", GetObjectIdentifier(host)}}); + if (service) { + data->Set("service_id", checkableId); + } - String redundancyGroup = dependency->GetRedundancyGroup(); + AddDataToHmSets(hMSets, RedisKey::DependencyNode, checkableId, data); + if (runtimeUpdates) { + AddObjectDataToRuntimeUpdates(*runtimeUpdates, checkableId, m_PrefixConfigObject + "dependency:node", data); + } + } - if (!redundancyGroup.IsEmpty()) { - Host::Ptr childHost; - Service::Ptr childService; - tie(childHost, childService) = GetHostService(dependency->GetChild()); + for (auto& dependencyGroup : checkable->GetDependencyGroups()) { + String edgeFromNodeId(checkableId); - String dependencyNodeChildId = HashValue( - (childService) - ? new Array({ m_EnvironmentId, GetObjectIdentifier(childHost), GetObjectIdentifier(childService) }) - : new Array({ m_EnvironmentId, GetObjectIdentifier(childHost) })); - String redundancyGroupId = HashValue(new Array({ - m_EnvironmentId, - redundancyGroup, - dependencyNodeChildId})); + if (dependencyGroup->IsRedundancyGroup()) { + auto redundancyGroupId(HashValue(new Array{m_EnvironmentId, dependencyGroup->GetCompositeKey()})); + dependencyGroup->SetIcingaDBIdentifier(redundancyGroupId); - redundancyGroupStates.emplace_back(redundancyGroupId); - Dictionary::Ptr groupStateData = new Dictionary({ + edgeFromNodeId = redundancyGroupId; + + // During the initial config sync, multiple children can depend on the same redundancy group, sync it only + // the first time it is encountered. Though, if this is a runtime update, we have to re-serialize and sync + // the redundancy group unconditionally, as we don't know whether it was already synced or the context that + // triggered this update. + if (runtimeUpdates || m_DumpedGlobals.DependencyGroup.IsNew(redundancyGroupId)) { + Dictionary::Ptr groupData(new Dictionary{ + {"environment_id", m_EnvironmentId}, + {"display_name", dependencyGroup->GetRedundancyGroupName()}, + }); + // Set/refresh the redundancy group data in the Redis HMSETs (redundancy_group database table). + AddDataToHmSets(hMSets, RedisKey::RedundancyGroup, redundancyGroupId, groupData); + + Dictionary::Ptr nodeData(new Dictionary{ + {"environment_id", m_EnvironmentId}, + {"redundancy_group_id", redundancyGroupId}, + }); + // Obviously, the redundancy group is part of some dependency chain, thus we have to generate + // dependency node entry for it as well. + AddDataToHmSets(hMSets, RedisKey::DependencyNode, redundancyGroupId, nodeData); + + if (runtimeUpdates) { + // Send the same data sent to the Redis HMSETs to the runtime updates stream as well. + AddObjectDataToRuntimeUpdates(*runtimeUpdates, redundancyGroupId, m_PrefixConfigObject + "redundancygroup", groupData); + AddObjectDataToRuntimeUpdates(*runtimeUpdates, redundancyGroupId, m_PrefixConfigObject + "dependency:node", nodeData); + } + } + + Dictionary::Ptr data(new Dictionary{ {"environment_id", m_EnvironmentId}, - {"redundancy_group_id", redundancyGroupId}, - {"failed", !((childService) ? childService->IsReachable() : childHost->IsReachable())}, - {"last_state_change", TimestampToMilliseconds(Utility::GetTime())}}); - redundancyGroupStates.emplace_back(JsonEncode(groupStateData)); + {"from_node_id", checkableId}, + {"to_node_id", redundancyGroupId}, + // All redundancy group members share the same state, thus use the group ID as a reference. + {"dependency_edge_state_id", redundancyGroupId}, + {"display_name", dependencyGroup->GetRedundancyGroupName()}, + }); - // TODO - // AddObjectDataToRuntimeUpdates(runtimeUpdates, redundancyGroupId, m_PrefixConfigObject + "redundancygroup:state", groupStateData); - // dataClone->Set("id", objectKey); // redundancyGroupId - // dataClone->Set("redis_key", redisKey); // m_PrefixConfigObject + "redundancygroup:state" - // dataClone->Set("runtime_type", "upsert"); - // runtimeUpdates.emplace_back(dataClone); + // Generate a dependency edge entry representing the connection between the Checkable and the redundancy + // group. This Checkable dependes on the redundancy group (is a child of it), thus the "dependency_edge_state_id" + // is set to the redundancy group ID. Note that if this group has multiple children, they all will have the + // same "dependency_edge_state_id" value. + auto edgeId(HashValue(new Array{checkableId, redundancyGroupId})); + AddDataToHmSets(hMSets, RedisKey::DependencyEdge, edgeId, data); + + if (runtimeUpdates) { + AddObjectDataToRuntimeUpdates(*runtimeUpdates, edgeId, m_PrefixConfigObject + "dependency:edge", data); + } + } + + auto dependencies(dependencyGroup->GetDependenciesForChild(checkable.get())); + // Sort the dependencies by their parent Checkable object to ensure that all dependency objects that share the + // same parent Checkable are placed next to each other in the container. See the while loop below for more info! + std::sort(dependencies.begin(), dependencies.end(), [](const Dependency::Ptr& lhs, const Dependency::Ptr& rhs) { + return lhs->GetParent() < rhs->GetParent(); + }); + + // Traverse through each dependency objects within the current dependency group the provided Checkable depend + // on and generate a dependency edge entry. The generated dependency edge "from_node_id" may vary depending on + // whether the dependency group is a redundancy group or not. If it's a redundancy group, the "from_node_id" + // will be the redundancy group ID; otherwise, it will be the current Checkable ID. However, the "to_node_id" + // value will always be the parent Checkable ID of the dependency object. + for (auto it(dependencies.begin()); it != dependencies.end(); /* no increment */) { + auto dependency(*it); + auto parent(dependency->GetParent()); + auto displayName(dependency->GetShortName()); + + // In case there are multiple Dependency objects with the same parent, these are merged into a single edge + // to prevent duplicate edges in the resulting graph. All objects with the same parent were placed next to + // each other by the sort function above. + // + // Additionally, the iterator for the surrounding loop is incremented by this loop: after it has finished, + // "it" will either point to the next dependency with a different parent or to the end of the container. + while (++it != dependencies.end() && (*it)->GetParent() == parent) { + displayName += ", " + (*it)->GetShortName(); + } + + Dictionary::Ptr data(new Dictionary{ + {"environment_id", m_EnvironmentId}, + {"from_node_id", edgeFromNodeId}, + {"to_node_id", GetObjectIdentifier(parent)}, + {"dependency_edge_state_id", HashValue(new Array{ + dependencyGroup->IsRedundancyGroup() + ? dependencyGroup->GetIcingaDBIdentifier() + : dependencyGroup->GetCompositeKey(), + GetObjectIdentifier(dependency->GetParent()), + })}, + {"display_name", std::move(displayName)}, + }); + + auto edgeId(HashValue(new Array{data->Get("from_node_id"), data->Get("to_node_id")})); + AddDataToHmSets(hMSets, RedisKey::DependencyEdge, edgeId, data); + + if (runtimeUpdates) { + AddObjectDataToRuntimeUpdates(*runtimeUpdates, edgeId, m_PrefixConfigObject + "dependency:edge", data); + } + } } } @@ -1632,32 +1622,6 @@ bool IcingaDB::PrepareObject(const ConfigObject::Ptr& object, Dictionary::Ptr& a return true; } - if (type == Dependency::TypeInstance) { - Dependency::Ptr dependency = static_pointer_cast(object); - String redundancyGroup = dependency->GetRedundancyGroup(); - - attributes->Set("name", GetObjectIdentifier(dependency)); - - if (!redundancyGroup.IsEmpty()) { - Host::Ptr childHost; - Service::Ptr childService; - tie(childHost, childService) = GetHostService(dependency->GetChild()); - - String dependencyNodeChildId = HashValue( - (childService) - ? new Array({ m_EnvironmentId, GetObjectIdentifier(childHost), GetObjectIdentifier(childService) }) - : new Array({ m_EnvironmentId, GetObjectIdentifier(childHost) })); - String redundancyGroupId = HashValue(new Array({ - m_EnvironmentId, - redundancyGroup, - dependencyNodeChildId})); - - attributes->Set("redundancy_group_id", redundancyGroupId); - } - - return true; - } - if (type == Downtime::TypeInstance) { Downtime::Ptr downtime = static_pointer_cast(object); @@ -3182,3 +3146,35 @@ void IcingaDB::DeleteRelationship(const String& id, const String& redisKeyWithou m_Rcon->FireAndForgetQueries(queries, Prio::Config); } + +/** + * Add the provided data to the Redis HMSETs map. + * + * Adds the provided data to the Redis HMSETs map for the provided Redis key. The actual Redis key is determined by + * the provided RedisKey enum. The data will be json encoded before being added to the Redis HMSETs map. + * + * @param hMSets The map of RedisConnection::Query you want to add the data to. + * @param redisKey The key of the Redis object you want to add the data to. + * @param id Unique Redis identifier for the provided data. + * @param data The actual data you want to add the Redis HMSETs map. + */ +void IcingaDB::AddDataToHmSets(std::map& hMSets, RedisKey redisKey, const String& id, const Dictionary::Ptr& data) const +{ + RedisConnection::Query* query; + switch (redisKey) { + case RedisKey::RedundancyGroup: + query = &hMSets[m_PrefixConfigObject + "redundancygroup"]; + break; + case RedisKey::DependencyNode: + query = &hMSets[m_PrefixConfigObject + "dependency:node"]; + break; + case RedisKey::DependencyEdge: + query = &hMSets[m_PrefixConfigObject + "dependency:edge"]; + break; + default: + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid RedisKey provided")); + } + + query->emplace_back(id); + query->emplace_back(JsonEncode(data)); +} diff --git a/lib/icingadb/icingadb.cpp b/lib/icingadb/icingadb.cpp index 3c623259b..8d3b9099b 100644 --- a/lib/icingadb/icingadb.cpp +++ b/lib/icingadb/icingadb.cpp @@ -38,8 +38,6 @@ IcingaDB::IcingaDB() m_PrefixConfigObject = "icinga:"; m_PrefixConfigCheckSum = "icinga:checksum:"; - - m_CheckablesToDependencies = new Dictionary(); } void IcingaDB::Validate(int types, const ValidationUtils& utils) diff --git a/lib/icingadb/icingadb.hpp b/lib/icingadb/icingadb.hpp index c5b499318..f24dcdc44 100644 --- a/lib/icingadb/icingadb.hpp +++ b/lib/icingadb/icingadb.hpp @@ -90,6 +90,15 @@ private: Full = Volatile | RuntimeOnly, }; + enum class RedisKey : uint8_t + { + RedundancyGroup, + DependencyNode, + DependencyEdge, + RedundancyGroupState, + DependencyEdgeState, + }; + void OnConnectedHandler(); void PublishStatsTimerHandler(); @@ -101,9 +110,10 @@ private: void DeleteKeys(const RedisConnection::Ptr& conn, const std::vector& keys, RedisConnection::QueryPriority priority); std::vector GetTypeOverwriteKeys(const String& type); std::vector GetTypeDumpSignalKeys(const Type::Ptr& type); + void InsertCheckableDependencies(const Checkable::Ptr& checkable, std::map& hMSets, + std::vector* runtimeUpdates); void InsertObjectDependencies(const ConfigObject::Ptr& object, const String typeName, std::map>& hMSets, std::vector& runtimeUpdates, bool runtimeUpdate); - void UpdateDependencyState(const Dependency::Ptr& dependency); void UpdateState(const Checkable::Ptr& checkable, StateUpdate mode); void SendConfigUpdate(const ConfigObject::Ptr& object, bool runtimeUpdate); void CreateConfigUpdate(const ConfigObject::Ptr& object, const String type, std::map>& hMSets, @@ -113,6 +123,7 @@ private: void AddObjectDataToRuntimeUpdates(std::vector& runtimeUpdates, const String& objectKey, const String& redisKey, const Dictionary::Ptr& data); void DeleteRelationship(const String& id, const String& redisKeyWithoutPrefix, bool hasChecksum = false); + void AddDataToHmSets(std::map& hMSets, RedisKey redisKey, const String& id, const Dictionary::Ptr& data) const; void SendSentNotification( const Notification::Ptr& notification, const Checkable::Ptr& checkable, const std::set& users, @@ -225,10 +236,8 @@ private: std::unordered_map m_Rcons; std::atomic_size_t m_PendingRcons; - Dictionary::Ptr m_CheckablesToDependencies; - struct { - DumpedGlobals CustomVar, ActionUrl, NotesUrl, IconImage; + DumpedGlobals CustomVar, ActionUrl, NotesUrl, IconImage, DependencyGroup; } m_DumpedGlobals; // m_EnvironmentId is shared across all IcingaDB objects (typically there is at most one, but it is perfectly fine From f502993eb4034262471744f04ef04f01d24e2b28 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 4 Dec 2024 16:47:32 +0100 Subject: [PATCH 212/415] IcingaDB: Sync dependencies states to Redis --- lib/icinga/dependency.hpp | 9 ++++ lib/icingadb/icingadb-objects.cpp | 87 +++++++++++++++++++++++++++++++ lib/icingadb/icingadb-utility.cpp | 59 +++++++++++++++++++++ lib/icingadb/icingadb.hpp | 3 ++ 4 files changed, 158 insertions(+) diff --git a/lib/icinga/dependency.hpp b/lib/icinga/dependency.hpp index 34667eec1..63bab92ad 100644 --- a/lib/icinga/dependency.hpp +++ b/lib/icinga/dependency.hpp @@ -180,6 +180,15 @@ protected: private: mutable std::mutex m_Mutex; + /** + * This identifier is used by Icinga DB to cache the unique hash of this dependency group. + * + * For redundancy groups, once Icinga DB sets this identifier, it will never change again for the lifetime + * of the object. For non-redundant dependency groups, this identifier is (mis)used to cache the shared edge + * state ID of the group. Specifically, non-redundant dependency groups are irrelevant for Icinga DB, so since + * this field isn't going to be used for anything else, we use it to cache the computed shared edge state ID. + * Likewise, if that gets set, it will never change again for the lifetime of the object as well. + */ String m_IcingaDBIdentifier; String m_RedundancyGroupName; MembersMap m_Members; diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index 33cf973ce..e61aee3ce 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -217,7 +217,9 @@ void IcingaDB::UpdateAllConfigObjects() // This allows us to wait on both types to be dumped before we send a config dump done signal for those keys. m_PrefixConfigObject + "dependency:node", m_PrefixConfigObject + "dependency:edge", + m_PrefixConfigObject + "dependency:edge:state", m_PrefixConfigObject + "redundancygroup", + m_PrefixConfigObject + "redundancygroup:state", }; DeleteKeys(m_Rcon, globalKeys, Prio::Config); DeleteKeys(m_Rcon, {"icinga:nextupdate:host", "icinga:nextupdate:service"}, Prio::Config); @@ -1344,6 +1346,90 @@ void IcingaDB::UpdateState(const Checkable::Ptr& checkable, StateUpdate mode) } } +/** + * Send dependencies state information of the given Checkable to Redis. + * + * If the dependencyGroup parameter is set, only the dependencies state of that group are sent. Otherwise, all + * dependency groups of the provided Checkable are processed. + * + * @param checkable The Checkable you want to send the dependencies state update for + * @param onlyDependencyGroup If set, send state updates only for this dependency group and its dependencies. + */ +void IcingaDB::UpdateDependenciesState(const Checkable::Ptr& checkable, const DependencyGroup::Ptr& onlyDependencyGroup) const +{ + if (!m_Rcon || !m_Rcon->IsConnected()) { + return; + } + + std::vector dependencyGroups{onlyDependencyGroup}; + if (!onlyDependencyGroup) { + dependencyGroups = checkable->GetDependencyGroups(); + if (dependencyGroups.empty()) { + return; + } + } + + RedisConnection::Queries streamStates; + auto addDependencyStateToStream([this, &streamStates](const String& redisKey, const Dictionary::Ptr& stateAttrs) { + RedisConnection::Query xAdd{ + "XADD", "icinga:runtime:state", "MAXLEN", "~", "1000000", "*", "runtime_type", "upsert", + "redis_key", redisKey + }; + ObjectLock olock(stateAttrs); + for (auto& [key, value] : stateAttrs) { + xAdd.emplace_back(key); + xAdd.emplace_back(IcingaToStreamValue(value)); + } + streamStates.emplace_back(std::move(xAdd)); + }); + + for (auto& dependencyGroup : dependencyGroups) { + bool isRedundancyGroup(dependencyGroup->IsRedundancyGroup()); + if (isRedundancyGroup && dependencyGroup->GetIcingaDBIdentifier().IsEmpty()) { + // Way too soon! The Icinga DB hash will be set during the initial config dump, but this state + // update seems to occur way too early. So, we've to skip it for now and wait for the next one. + // The m_ConfigDumpInProgress flag is probably still set to true at this point! + continue; + } + + auto dependencies(dependencyGroup->GetDependenciesForChild(checkable.get())); + std::sort(dependencies.begin(), dependencies.end(), [](const Dependency::Ptr& lhs, const Dependency::Ptr& rhs) { + return lhs->GetParent() < rhs->GetParent(); + }); + for (auto it(dependencies.begin()); it != dependencies.end(); /* no increment */) { + const auto& dependency(*it); + + Dictionary::Ptr stateAttrs; + // Note: The following loop is intended to cover some possible special cases but may not occur in practice + // that often. That is, having two or more dependency objects that point to the same parent Checkable. + // So, traverse all those duplicates and merge their relevant state information into a single edge. + for (; it != dependencies.end() && (*it)->GetParent() == dependency->GetParent(); ++it) { + if (!stateAttrs || stateAttrs->Get("failed") == false) { + stateAttrs = SerializeDependencyEdgeState(dependencyGroup, *it); + } + } + + addDependencyStateToStream(m_PrefixConfigObject + "dependency:edge:state", stateAttrs); + } + + if (isRedundancyGroup) { + Dictionary::Ptr stateAttrs(SerializeRedundancyGroupState(dependencyGroup)); + + Dictionary::Ptr sharedGroupState(stateAttrs->ShallowClone()); + sharedGroupState->Remove("redundancy_group_id"); + sharedGroupState->Remove("is_reachable"); + sharedGroupState->Remove("last_state_change"); + + addDependencyStateToStream(m_PrefixConfigObject + "redundancygroup:state", stateAttrs); + addDependencyStateToStream(m_PrefixConfigObject + "dependency:edge:state", sharedGroupState); + } + } + + if (!streamStates.empty()) { + m_Rcon->FireAndForgetQueries(std::move(streamStates), Prio::RuntimeStateStream, {0, 1}); + } +} + // Used to update a single object, used for runtime updates void IcingaDB::SendConfigUpdate(const ConfigObject::Ptr& object, bool runtimeUpdate) { @@ -2933,6 +3019,7 @@ void IcingaDB::ReachabilityChangeHandler(const std::set& childre for (const IcingaDB::Ptr& rw : ConfigType::GetObjectsByType()) { for (auto& checkable : children) { rw->UpdateState(checkable, StateUpdate::Full); + rw->UpdateDependenciesState(checkable); } } } diff --git a/lib/icingadb/icingadb-utility.cpp b/lib/icingadb/icingadb-utility.cpp index 35f503ab5..f53055a50 100644 --- a/lib/icingadb/icingadb-utility.cpp +++ b/lib/icingadb/icingadb-utility.cpp @@ -159,6 +159,65 @@ Dictionary::Ptr IcingaDB::SerializeVars(const Dictionary::Ptr& vars) return res; } +/** + * Serialize a dependency edge state for Icinga DB + * + * @param dependencyGroup The state of the group the dependency is part of. + * @param dep The dependency object to serialize. + * + * @return A dictionary with the serialized state. + */ +Dictionary::Ptr IcingaDB::SerializeDependencyEdgeState(const DependencyGroup::Ptr& dependencyGroup, const Dependency::Ptr& dep) +{ + String edgeStateId; + // The edge state ID is computed a bit differently depending on whether this is for a redundancy group or not. + // For redundancy groups, the state ID is supposed to represent the connection state between the redundancy group + // and the parent Checkable of the given dependency. Hence, the outcome will always be different for each parent + // Checkable of the redundancy group. + if (dependencyGroup->IsRedundancyGroup()) { + edgeStateId = HashValue(new Array{ + dependencyGroup->GetIcingaDBIdentifier(), + GetObjectIdentifier(dep->GetParent()), + }); + } else if (dependencyGroup->GetIcingaDBIdentifier().IsEmpty()) { + // For non-redundant dependency groups, on the other hand, all dependency objects within that group will + // always have the same parent Checkable. Likewise, the state ID will be always the same as well it doesn't + // matter which dependency object is used to compute it. Therefore, it's sufficient to compute it only once + // and all the other dependency objects can reuse the cached state ID. + edgeStateId = HashValue(new Array{dependencyGroup->GetCompositeKey(), GetObjectIdentifier(dep->GetParent())}); + dependencyGroup->SetIcingaDBIdentifier(edgeStateId); + } else { + // Use the already computed state ID for the dependency group. + edgeStateId = dependencyGroup->GetIcingaDBIdentifier(); + } + + return new Dictionary{ + {"id", std::move(edgeStateId)}, + {"environment_id", m_EnvironmentId}, + {"failed", !dep->IsAvailable(DependencyState) || !dep->GetParent()->IsReachable()} + }; +} + +/** + * Serialize the provided redundancy group state attributes. + * + * @param redundancyGroup The redundancy group object to serialize the state for. + * + * @return A dictionary with the serialized redundancy group state. + */ +Dictionary::Ptr IcingaDB::SerializeRedundancyGroupState(const DependencyGroup::Ptr& redundancyGroup) +{ + auto state(redundancyGroup->GetState()); + return new Dictionary{ + {"id", redundancyGroup->GetIcingaDBIdentifier()}, + {"environment_id", m_EnvironmentId}, + {"redundancy_group_id", redundancyGroup->GetIcingaDBIdentifier()}, + {"failed", !state.Reachable || !state.OK}, + {"is_reachable", state.Reachable}, + {"last_state_change", TimestampToMilliseconds(Utility::GetTime())}, + }; +} + const char* IcingaDB::GetNotificationTypeByEnum(NotificationType type) { switch (type) { diff --git a/lib/icingadb/icingadb.hpp b/lib/icingadb/icingadb.hpp index f24dcdc44..73ee4e8ae 100644 --- a/lib/icingadb/icingadb.hpp +++ b/lib/icingadb/icingadb.hpp @@ -114,6 +114,7 @@ private: std::vector* runtimeUpdates); void InsertObjectDependencies(const ConfigObject::Ptr& object, const String typeName, std::map>& hMSets, std::vector& runtimeUpdates, bool runtimeUpdate); + void UpdateDependenciesState(const Checkable::Ptr& checkable, const DependencyGroup::Ptr& onlyDependencyGroup = nullptr) const; void UpdateState(const Checkable::Ptr& checkable, StateUpdate mode); void SendConfigUpdate(const ConfigObject::Ptr& object, bool runtimeUpdate); void CreateConfigUpdate(const ConfigObject::Ptr& object, const String type, std::map>& hMSets, @@ -169,6 +170,8 @@ private: static String CalcEventID(const char* eventType, const ConfigObject::Ptr& object, double eventTime = 0, NotificationType nt = NotificationType(0)); static const char* GetNotificationTypeByEnum(NotificationType type); static Dictionary::Ptr SerializeVars(const Dictionary::Ptr& vars); + static Dictionary::Ptr SerializeDependencyEdgeState(const DependencyGroup::Ptr& dependencyGroup, const Dependency::Ptr& dep); + static Dictionary::Ptr SerializeRedundancyGroupState(const DependencyGroup::Ptr& redundancyGroup); static String HashValue(const Value& value); static String HashValue(const Value& value, const std::set& propertiesBlacklist, bool propertiesWhitelist = false); From db3f8dec275e135a43f7ce01bdcced5cd297f94b Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 5 Dec 2024 16:21:32 +0100 Subject: [PATCH 213/415] IcingaDB: Sync dependencies initial states on config dump --- lib/icingadb/icingadb-objects.cpp | 45 ++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 7 deletions(-) diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index e61aee3ce..ecae7c201 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -1147,6 +1147,9 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S * in the dependency graph. * - RedisKey::DependencyEdge: Dependency edge information representing all connections between the nodes. * - RedisKey::RedundancyGroup: Redundancy group data representing all redundancy groups in the graph. + * - RedisKey::RedundancyGroupState: State information for redundancy groups. + * - RedisKey::DependencyEdgeState: State information for (each) dependency edge. Multiple edges may share the + * same state. * * For initial dumps, it shouldn't be necessary to set the `runtimeUpdates` parameter. * @@ -1185,8 +1188,14 @@ void IcingaDB::InsertCheckableDependencies( for (auto& dependencyGroup : checkable->GetDependencyGroups()) { String edgeFromNodeId(checkableId); + bool syncSharedEdgeState(false); - if (dependencyGroup->IsRedundancyGroup()) { + if (!dependencyGroup->IsRedundancyGroup()) { + // Non-redundant dependency groups are just placeholders and never get synced to Redis, thus just figure + // out whether we have to sync the shared edge state. For runtime updates the states are sent via the + // UpdateDependenciesState() method, thus we don't have to sync them here. + syncSharedEdgeState = !runtimeUpdates && m_DumpedGlobals.DependencyGroup.IsNew(dependencyGroup->GetCompositeKey()); + } else { auto redundancyGroupId(HashValue(new Array{m_EnvironmentId, dependencyGroup->GetCompositeKey()})); dependencyGroup->SetIcingaDBIdentifier(redundancyGroupId); @@ -1216,6 +1225,20 @@ void IcingaDB::InsertCheckableDependencies( // Send the same data sent to the Redis HMSETs to the runtime updates stream as well. AddObjectDataToRuntimeUpdates(*runtimeUpdates, redundancyGroupId, m_PrefixConfigObject + "redundancygroup", groupData); AddObjectDataToRuntimeUpdates(*runtimeUpdates, redundancyGroupId, m_PrefixConfigObject + "dependency:node", nodeData); + } else { + syncSharedEdgeState = true; + + // Serialize and sync the redundancy group state information a) to the RedundancyGroupState and b) + // to the DependencyEdgeState HMSETs. The latter is shared by all child Checkables of the current + // redundancy group, and since they all depend on the redundancy group, the state of that group is + // basically the state of the dependency edges between the children and the redundancy group. + auto stateAttrs(SerializeRedundancyGroupState(dependencyGroup)); + AddDataToHmSets(hMSets, RedisKey::RedundancyGroupState, redundancyGroupId, stateAttrs); + AddDataToHmSets(hMSets, RedisKey::DependencyEdgeState, redundancyGroupId, Dictionary::Ptr(new Dictionary{ + {"id", redundancyGroupId}, + {"environment_id", m_EnvironmentId}, + {"failed", stateAttrs->Get("failed")}, + })); } } @@ -1257,6 +1280,8 @@ void IcingaDB::InsertCheckableDependencies( auto parent(dependency->GetParent()); auto displayName(dependency->GetShortName()); + Dictionary::Ptr edgeStateAttrs(SerializeDependencyEdgeState(dependencyGroup, dependency)); + // In case there are multiple Dependency objects with the same parent, these are merged into a single edge // to prevent duplicate edges in the resulting graph. All objects with the same parent were placed next to // each other by the sort function above. @@ -1265,18 +1290,16 @@ void IcingaDB::InsertCheckableDependencies( // "it" will either point to the next dependency with a different parent or to the end of the container. while (++it != dependencies.end() && (*it)->GetParent() == parent) { displayName += ", " + (*it)->GetShortName(); + if (syncSharedEdgeState && edgeStateAttrs->Get("failed") == false) { + edgeStateAttrs = SerializeDependencyEdgeState(dependencyGroup, *it); + } } Dictionary::Ptr data(new Dictionary{ {"environment_id", m_EnvironmentId}, {"from_node_id", edgeFromNodeId}, {"to_node_id", GetObjectIdentifier(parent)}, - {"dependency_edge_state_id", HashValue(new Array{ - dependencyGroup->IsRedundancyGroup() - ? dependencyGroup->GetIcingaDBIdentifier() - : dependencyGroup->GetCompositeKey(), - GetObjectIdentifier(dependency->GetParent()), - })}, + {"dependency_edge_state_id", edgeStateAttrs->Get("id")}, {"display_name", std::move(displayName)}, }); @@ -1285,6 +1308,8 @@ void IcingaDB::InsertCheckableDependencies( if (runtimeUpdates) { AddObjectDataToRuntimeUpdates(*runtimeUpdates, edgeId, m_PrefixConfigObject + "dependency:edge", data); + } else if (syncSharedEdgeState) { + AddDataToHmSets(hMSets, RedisKey::DependencyEdgeState, edgeStateAttrs->Get("id"), edgeStateAttrs); } } } @@ -3258,6 +3283,12 @@ void IcingaDB::AddDataToHmSets(std::map& hMSets, case RedisKey::DependencyEdge: query = &hMSets[m_PrefixConfigObject + "dependency:edge"]; break; + case RedisKey::RedundancyGroupState: + query = &hMSets[m_PrefixConfigObject + "redundancygroup:state"]; + break; + case RedisKey::DependencyEdgeState: + query = &hMSets[m_PrefixConfigObject + "dependency:edge:state"]; + break; default: BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid RedisKey provided")); } From aed1bb6294a07d088d051339edebdb44619e2ec7 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 24 Feb 2025 13:11:12 +0100 Subject: [PATCH 214/415] IcingaDB: Introduce `ExecuteRedisTransaction()` helper method --- lib/icingadb/icingadb-objects.cpp | 122 +++++++++++++----------------- lib/icingadb/icingadb.hpp | 3 + 2 files changed, 57 insertions(+), 68 deletions(-) diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index ecae7c201..e407373fa 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -273,11 +273,6 @@ void IcingaDB::UpdateAllConfigObjects() upqObjectType.ParallelFor(objectChunks, [&](decltype(objectChunks)::const_reference chunk) { std::map> hMSets; - // Two values are appended per object: Object ID (Hash encoded) and Object State (IcingaDB::SerializeState() -> JSON encoded) - std::vector states = {"HMSET", m_PrefixConfigObject + lcType + ":state"}; - // Two values are appended per object: Object ID (Hash encoded) and State Checksum ({ "checksum": checksum } -> JSON encoded) - std::vector statesChksms = {"HMSET", m_PrefixConfigCheckSum + lcType + ":state"}; - std::vector > transaction = {{"MULTI"}}; std::vector hostZAdds = {"ZADD", "icinga:nextupdate:host"}, serviceZAdds = {"ZADD", "icinga:nextupdate:service"}; auto skimObjects ([&]() { @@ -317,9 +312,11 @@ void IcingaDB::UpdateAllConfigObjects() String objectKey = GetObjectIdentifier(object); Dictionary::Ptr state = SerializeState(dynamic_pointer_cast(object)); + auto& states = hMSets[m_PrefixConfigObject + lcType + ":state"]; states.emplace_back(objectKey); states.emplace_back(JsonEncode(state)); + auto& statesChksms = hMSets[m_PrefixConfigCheckSum + lcType + ":state"]; statesChksms.emplace_back(objectKey); statesChksms.emplace_back(JsonEncode(new Dictionary({{"checksum", HashValue(state)}}))); } @@ -328,27 +325,9 @@ void IcingaDB::UpdateAllConfigObjects() if (!(bulkCounter % 100)) { skimObjects(); - for (auto& kv : hMSets) { - if (!kv.second.empty()) { - kv.second.insert(kv.second.begin(), {"HMSET", kv.first}); - transaction.emplace_back(std::move(kv.second)); - } - } - - if (states.size() > 2) { - transaction.emplace_back(std::move(states)); - transaction.emplace_back(std::move(statesChksms)); - states = {"HMSET", m_PrefixConfigObject + lcType + ":state"}; - statesChksms = {"HMSET", m_PrefixConfigCheckSum + lcType + ":state"}; - } + ExecuteRedisTransaction(rcon, hMSets, {}); hMSets = decltype(hMSets)(); - - if (transaction.size() > 1) { - transaction.push_back({"EXEC"}); - rcon->FireAndForgetQueries(std::move(transaction), Prio::Config); - transaction = {{"MULTI"}}; - } } auto checkable (dynamic_pointer_cast(object)); @@ -371,22 +350,7 @@ void IcingaDB::UpdateAllConfigObjects() skimObjects(); - for (auto& kv : hMSets) { - if (!kv.second.empty()) { - kv.second.insert(kv.second.begin(), {"HMSET", kv.first}); - transaction.emplace_back(std::move(kv.second)); - } - } - - if (states.size() > 2) { - transaction.emplace_back(std::move(states)); - transaction.emplace_back(std::move(statesChksms)); - } - - if (transaction.size() > 1) { - transaction.push_back({"EXEC"}); - rcon->FireAndForgetQueries(std::move(transaction), Prio::Config); - } + ExecuteRedisTransaction(rcon, hMSets, {}); for (auto zAdds : {&hostZAdds, &serviceZAdds}) { if (zAdds->size() > 2u) { @@ -1472,34 +1436,7 @@ void IcingaDB::SendConfigUpdate(const ConfigObject::Ptr& object, bool runtimeUpd UpdateState(checkable, runtimeUpdate ? StateUpdate::Full : StateUpdate::Volatile); } - std::vector > transaction = {{"MULTI"}}; - - for (auto& kv : hMSets) { - if (!kv.second.empty()) { - kv.second.insert(kv.second.begin(), {"HMSET", kv.first}); - transaction.emplace_back(std::move(kv.second)); - } - } - - for (auto& objectAttributes : runtimeUpdates) { - std::vector xAdd({"XADD", "icinga:runtime", "MAXLEN", "~", "1000000", "*"}); - ObjectLock olock(objectAttributes); - - for (const Dictionary::Pair& kv : objectAttributes) { - String value = IcingaToStreamValue(kv.second); - if (!value.IsEmpty()) { - xAdd.emplace_back(kv.first); - xAdd.emplace_back(value); - } - } - - transaction.emplace_back(std::move(xAdd)); - } - - if (transaction.size() > 1) { - transaction.push_back({"EXEC"}); - m_Rcon->FireAndForgetQueries(std::move(transaction), Prio::Config, {1}); - } + ExecuteRedisTransaction(m_Rcon, hMSets, runtimeUpdates); if (checkable) { SendNextUpdate(checkable); @@ -3296,3 +3233,52 @@ void IcingaDB::AddDataToHmSets(std::map& hMSets, query->emplace_back(id); query->emplace_back(JsonEncode(data)); } + +/** + * Execute the provided HMSET values and runtime updates in a single Redis transaction on the provided Redis connection. + * + * The HMSETs should just contain the necessary key value pairs to be set in Redis, i.e, without the HMSET command + * itself. This function will then go through each of the map keys and prepend the HMSET command when transforming the + * map into valid Redis queries. Likewise, the runtime updates should just contain the key value pairs to be streamed + * to the icinga:runtime pipeline, and this function will generate a XADD query for each one of the vector elements. + * + * @param rcon The Redis connection to execute the transaction on. + * @param hMSets A map of Redis keys and their respective HMSET values. + * @param runtimeUpdates A list of dictionaries to be sent to the icinga:runtime stream. + */ +void IcingaDB::ExecuteRedisTransaction(const RedisConnection::Ptr& rcon, std::map& hMSets, + const std::vector& runtimeUpdates) +{ + RedisConnection::Queries transaction{{"MULTI"}}; + for (auto& [redisKey, query] : hMSets) { + if (!query.empty()) { + query.insert(query.begin(), {"HSET", redisKey}); + transaction.emplace_back(std::move(query)); + } + } + + for (auto& attrs : runtimeUpdates) { + RedisConnection::Query xAdd{"XADD", "icinga:runtime", "MAXLEN", "~", "1000000", "*"}; + + ObjectLock olock(attrs); + for (auto& [key, value] : attrs) { + if (auto streamVal(IcingaToStreamValue(value)); !streamVal.IsEmpty()) { + xAdd.emplace_back(key); + xAdd.emplace_back(std::move(streamVal)); + } + } + + transaction.emplace_back(std::move(xAdd)); + } + + if (transaction.size() > 1) { + transaction.emplace_back(RedisConnection::Query{"EXEC"}); + if (!runtimeUpdates.empty()) { + rcon->FireAndForgetQueries(std::move(transaction), Prio::Config, {1}); + } else { + // This is likely triggered by the initial Redis config dump, so a) we don't need to record the number of + // affected objects and b) we don't really know how many objects are going to be affected by this tx. + rcon->FireAndForgetQueries(std::move(transaction), Prio::Config); + } + } +} diff --git a/lib/icingadb/icingadb.hpp b/lib/icingadb/icingadb.hpp index 73ee4e8ae..d8f7843a5 100644 --- a/lib/icingadb/icingadb.hpp +++ b/lib/icingadb/icingadb.hpp @@ -210,6 +210,9 @@ private: static void CommandArgumentsChangedHandler(const ConfigObject::Ptr& command, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues); static void CustomVarsChangedHandler(const ConfigObject::Ptr& object, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues); + static void ExecuteRedisTransaction(const RedisConnection::Ptr& rcon, std::map& hMSets, + const std::vector& runtimeUpdates); + void AssertOnWorkQueue(); void ExceptionHandler(boost::exception_ptr exp); From 26f46fe021f8c993e9d9e1cf278180e6a0afef0c Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Fri, 7 Feb 2025 11:52:15 +0100 Subject: [PATCH 215/415] Simplify dependency group registration Co-Authored-By: Yonas Habteab --- lib/icinga/checkable-dependency.cpp | 90 +++++++++++++++--- lib/icinga/checkable.hpp | 7 +- lib/icinga/dependency-group.cpp | 140 +++++++++------------------- lib/icinga/dependency.cpp | 4 +- lib/icinga/dependency.hpp | 52 ++++++----- 5 files changed, 152 insertions(+), 141 deletions(-) diff --git a/lib/icinga/checkable-dependency.cpp b/lib/icinga/checkable-dependency.cpp index 0904c09cf..21cbcd71f 100644 --- a/lib/icinga/checkable-dependency.cpp +++ b/lib/icinga/checkable-dependency.cpp @@ -15,29 +15,91 @@ using namespace icinga; */ static constexpr int l_MaxDependencyRecursionLevel(256); -void Checkable::AddDependencyGroup(const DependencyGroup::Ptr& dependencyGroup) -{ - std::unique_lock lock(m_DependencyMutex); - m_DependencyGroups.insert(dependencyGroup); -} - -void Checkable::RemoveDependencyGroup(const DependencyGroup::Ptr& dependencyGroup) -{ - std::unique_lock lock(m_DependencyMutex); - m_DependencyGroups.erase(dependencyGroup); -} - std::vector Checkable::GetDependencyGroups() const { std::lock_guard lock(m_DependencyMutex); - return {m_DependencyGroups.begin(), m_DependencyGroups.end()}; + std::vector dependencyGroups; + for (const auto& [_, dependencyGroup] : m_DependencyGroups) { + dependencyGroups.emplace_back(dependencyGroup); + } + return dependencyGroups; +} + +/** + * Get the key for the provided dependency group. + * + * The key is either the parent Checkable object or the redundancy group name of the dependency object. + * This is used to uniquely identify the dependency group within a given Checkable object. + * + * @param dependency The dependency to get the key for. + * + * @return - Returns the key for the provided dependency group. + */ +static std::variant GetDependencyGroupKey(const Dependency::Ptr& dependency) +{ + if (auto redundancyGroup(dependency->GetRedundancyGroup()); !redundancyGroup.IsEmpty()) { + return std::move(redundancyGroup); + } + + return dependency->GetParent().get(); +} + +/** + * Add the provided dependency to the current Checkable list of dependencies. + * + * @param dependency The dependency to add. + */ +void Checkable::AddDependency(const Dependency::Ptr& dependency) +{ + std::lock_guard lock(m_DependencyMutex); + + auto dependencyGroupKey(GetDependencyGroupKey(dependency)); + std::set dependencies; + if (auto it(m_DependencyGroups.find(dependencyGroupKey)); it != m_DependencyGroups.end()) { + dependencies = DependencyGroup::Unregister(it->second, this); + m_DependencyGroups.erase(it); + } + + dependencies.emplace(dependency); + + m_DependencyGroups.emplace( + dependencyGroupKey, + DependencyGroup::Register(new DependencyGroup(dependency->GetRedundancyGroup(), dependencies)) + ); +} + +/** + * Remove the provided dependency from the current Checkable list of dependencies. + * + * @param dependency The dependency to remove. + */ +void Checkable::RemoveDependency(const Dependency::Ptr& dependency) +{ + std::lock_guard lock(m_DependencyMutex); + + auto dependencyGroupKey(GetDependencyGroupKey(dependency)); + auto it = m_DependencyGroups.find(dependencyGroupKey); + if (it == m_DependencyGroups.end()) { + return; + } + + std::set dependencies(DependencyGroup::Unregister(it->second, this)); + m_DependencyGroups.erase(it); + dependencies.erase(dependency); + + if (!dependencies.empty()) { + m_DependencyGroups.emplace( + dependencyGroupKey, + DependencyGroup::Register(new DependencyGroup(dependency->GetRedundancyGroup(), dependencies)) + ); + } } std::vector Checkable::GetDependencies() const { std::unique_lock lock(m_DependencyMutex); std::vector dependencies; - for (const auto& dependencyGroup : m_DependencyGroups) { + for (const auto& [_, dependencyGroup] : m_DependencyGroups) { auto tmpDependencies(dependencyGroup->GetDependenciesForChild(this)); dependencies.insert(dependencies.end(), tmpDependencies.begin(), tmpDependencies.end()); } diff --git a/lib/icinga/checkable.hpp b/lib/icinga/checkable.hpp index 2b9014143..2d29bbec0 100644 --- a/lib/icinga/checkable.hpp +++ b/lib/icinga/checkable.hpp @@ -18,6 +18,7 @@ #include #include #include +#include namespace icinga { @@ -185,9 +186,9 @@ public: bool IsFlapping() const; /* Dependencies */ - void AddDependencyGroup(const intrusive_ptr& dependencyGroup); - void RemoveDependencyGroup(const intrusive_ptr& dependencyGroup); std::vector> GetDependencyGroups() const; + void AddDependency(const intrusive_ptr& dependency); + void RemoveDependency(const intrusive_ptr& dependency); std::vector > GetDependencies() const; bool HasAnyDependencies() const; @@ -249,7 +250,7 @@ private: /* Dependencies */ mutable std::mutex m_DependencyMutex; - std::set> m_DependencyGroups; + std::map, intrusive_ptr> m_DependencyGroups; std::set > m_ReverseDependencies; void GetAllChildrenInternal(std::set& seenChildren, int level = 0) const; diff --git a/lib/icinga/dependency-group.cpp b/lib/icinga/dependency-group.cpp index 8b5aac92d..eabe69a0b 100644 --- a/lib/icinga/dependency-group.cpp +++ b/lib/icinga/dependency-group.cpp @@ -9,106 +9,52 @@ std::mutex DependencyGroup::m_RegistryMutex; DependencyGroup::RegistryType DependencyGroup::m_Registry; /** - * Refresh the global registry of dependency groups. + * Register the provided dependency group to the global dependency group registry. * - * Registers the provided dependency object to an existing dependency group with the same redundancy - * group name (if any), or creates a new one and registers it to the child Checkable and the registry. + * In case there is already an identical dependency group in the registry, the provided dependency group is merged + * with the existing one, and that group is returned. Otherwise, the provided dependency group is registered as is, + * and it's returned. * - * Note: This is a helper function intended for internal use only, and you should acquire the global registry mutex - * before calling this function. - * - * @param dependency The dependency object to refresh the registry for. - * @param unregister A flag indicating whether the provided dependency object should be unregistered from the registry. + * @param dependencyGroup The dependency group to register. */ -void DependencyGroup::RefreshRegistry(const Dependency::Ptr& dependency, bool unregister) +DependencyGroup::Ptr DependencyGroup::Register(const DependencyGroup::Ptr& dependencyGroup) { - auto registerRedundancyGroup = [](const DependencyGroup::Ptr& dependencyGroup) { - if (auto [it, inserted](m_Registry.insert(dependencyGroup.get())); !inserted) { - DependencyGroup::Ptr existingGroup(*it); - dependencyGroup->CopyDependenciesTo(existingGroup); - } - }; - - // Retrieve all the dependency groups with the same redundancy group name of the provided dependency object. - // This allows us to shorten the lookup for the _one_ optimal group to (un)register the dependency from/to. - auto [begin, end] = m_Registry.get<1>().equal_range(dependency->GetRedundancyGroup()); - for (auto it(begin); it != end; ++it) { - DependencyGroup::Ptr existingGroup(*it); - auto child(dependency->GetChild()); - if (auto dependencies(existingGroup->GetDependenciesForChild(child.get())); !dependencies.empty()) { - m_Registry.erase(existingGroup->GetCompositeKey()); // Will be re-registered when needed down below. - if (unregister) { - existingGroup->RemoveDependency(dependency); - // Remove the connection between the child Checkable and the dependency group if it has no members - // left or the above removed member was the only member of the group that the child depended on. - if (existingGroup->IsEmpty() || dependencies.size() == 1) { - child->RemoveDependencyGroup(existingGroup); - } - } - - size_t totalDependencies(existingGroup->GetDependenciesCount()); - // If the existing dependency group has an identical member already, or the child Checkable of the - // dependency object is the only member of it (totalDependencies == dependencies.size()), we can simply - // add the dependency object to the existing group. - if (!unregister && (existingGroup->HasParentWithConfig(dependency) || totalDependencies == dependencies.size())) { - existingGroup->AddDependency(dependency); - } else if (!unregister || (dependencies.size() > 1 && totalDependencies >= dependencies.size())) { - // The child Checkable is going to have a new dependency group, so we must detach the existing one. - child->RemoveDependencyGroup(existingGroup); - - Ptr replacementGroup(unregister ? nullptr : new DependencyGroup(existingGroup->GetRedundancyGroupName(), dependency)); - for (auto& existingDependency : dependencies) { - if (existingDependency != dependency) { - existingGroup->RemoveDependency(existingDependency); - if (replacementGroup) { - replacementGroup->AddDependency(existingDependency); - } else { - replacementGroup = new DependencyGroup(existingGroup->GetRedundancyGroupName(), existingDependency); - } - } - } - - child->AddDependencyGroup(replacementGroup); - registerRedundancyGroup(replacementGroup); - } - - if (!existingGroup->IsEmpty()) { - registerRedundancyGroup(existingGroup); - } - return; - } - } - - if (!unregister) { - // We couldn't find any existing dependency group to register the dependency to, so we must - // initiate a new one and attach it to the child Checkable and register to the global registry. - DependencyGroup::Ptr newGroup(new DependencyGroup(dependency->GetRedundancyGroup())); - newGroup->AddDependency(dependency); - dependency->GetChild()->AddDependencyGroup(newGroup); - registerRedundancyGroup(newGroup); + std::lock_guard lock(m_RegistryMutex); + if (auto [it, inserted] = m_Registry.insert(dependencyGroup); !inserted) { + dependencyGroup->CopyDependenciesTo(*it); + return *it; } + return dependencyGroup; } /** - * Register the provided dependency to the global dependency group registry. + * Detach the provided child Checkable from the specified dependency group. * - * @param dependency The dependency to register. + * Unregisters all the dependency objects the child Checkable depends on from the provided dependency group and + * removes the dependency group from the global registry if it becomes empty afterward. + * + * @param dependencyGroup The dependency group to unregister the child Checkable from. + * @param child The child Checkable to detach from the dependency group. + * + * @return - Returns the dependency objects of the child Checkable that were member of the provided dependency group. */ -void DependencyGroup::Register(const Dependency::Ptr& dependency) +std::set DependencyGroup::Unregister(const DependencyGroup::Ptr& dependencyGroup, const Checkable::Ptr& child) { std::lock_guard lock(m_RegistryMutex); - RefreshRegistry(dependency, false); -} + std::vector dependencies; + if (auto it(m_Registry.find(dependencyGroup)); it != m_Registry.end()) { + const auto& existingGroup(*it); + dependencies = existingGroup->GetDependenciesForChild(child.get()); -/** - * Unregister the provided dependency from the dependency group it was member of. - * - * @param dependency The dependency to unregister. - */ -void DependencyGroup::Unregister(const Dependency::Ptr& dependency) -{ - std::lock_guard lock(m_RegistryMutex); - RefreshRegistry(dependency, true); + for (const auto& dependency : dependencies) { + existingGroup->RemoveDependency(dependency); + } + + if (existingGroup->IsEmpty()) { + m_Registry.erase(it); + } + } + return {dependencies.begin(), dependencies.end()}; } /** @@ -126,6 +72,13 @@ DependencyGroup::DependencyGroup(String name): m_RedundancyGroupName(std::move(n { } +DependencyGroup::DependencyGroup(String name, const std::set& dependencies): m_RedundancyGroupName(std::move(name)) +{ + for (const auto& dependency : dependencies) { + AddDependency(dependency); + } +} + /** * Create a composite key for the provided dependency. * @@ -245,17 +198,10 @@ void DependencyGroup::CopyDependenciesTo(const DependencyGroup::Ptr& dest) VERIFY(this != dest); // Prevent from doing something stupid, i.e. deadlocking ourselves. std::lock_guard lock(m_Mutex); - DependencyGroup::Ptr thisPtr(this); // Just in case the Checkable below was our last reference. for (auto& [_, children] : m_Members) { - Checkable::Ptr previousChild; - for (auto& [checkable, dependency] : children) { - dest->AddDependency(dependency); - if (!previousChild || previousChild != checkable) { - previousChild = dependency->GetChild(); - previousChild->RemoveDependencyGroup(thisPtr); - previousChild->AddDependencyGroup(dest); - } - } + std::for_each(children.begin(), children.end(), [&dest](const auto& pair) { + dest->AddDependency(pair.second); + }); } } diff --git a/lib/icinga/dependency.cpp b/lib/icinga/dependency.cpp index 2f2482136..a9a7bf372 100644 --- a/lib/icinga/dependency.cpp +++ b/lib/icinga/dependency.cpp @@ -251,7 +251,7 @@ void Dependency::OnAllConfigLoaded() // InitChildParentReferences() has to be called before. VERIFY(m_Child && m_Parent); - DependencyGroup::Register(this); + m_Child->AddDependency(this); m_Parent->AddReverseDependency(this); } @@ -259,7 +259,7 @@ void Dependency::Stop(bool runtimeRemoved) { ObjectImpl::Stop(runtimeRemoved); - DependencyGroup::Unregister(this); + GetChild()->RemoveDependency(this); GetParent()->RemoveReverseDependency(this); } diff --git a/lib/icinga/dependency.hpp b/lib/icinga/dependency.hpp index 63bab92ad..0698a3e0a 100644 --- a/lib/icinga/dependency.hpp +++ b/lib/icinga/dependency.hpp @@ -8,9 +8,6 @@ #include "icinga/i2-icinga.hpp" #include "icinga/dependency-ti.hpp" #include "icinga/timeperiod.hpp" -#include -#include -#include #include #include #include @@ -136,9 +133,10 @@ public: using MembersMap = std::map; explicit DependencyGroup(String name); + DependencyGroup(String name, const std::set& dependencies); - static void Register(const Dependency::Ptr& dependency); - static void Unregister(const Dependency::Ptr& dependency); + static DependencyGroup::Ptr Register(const DependencyGroup::Ptr& dependencyGroup); + static std::set Unregister(const DependencyGroup::Ptr& dependencyGroup, const Checkable::Ptr& child); static size_t GetRegistrySize(); static CompositeKeyType MakeCompositeKeyFor(const Dependency::Ptr& dependency); @@ -176,7 +174,29 @@ protected: void RemoveDependency(const Dependency::Ptr& dependency); void CopyDependenciesTo(const DependencyGroup::Ptr& dest); - static void RefreshRegistry(const Dependency::Ptr& dependency, bool unregister); + struct Hash + { + size_t operator()(const DependencyGroup::Ptr& dependencyGroup) const + { + size_t hash = 0; + for (const auto& [key, group] : dependencyGroup->m_Members) { + boost::hash_combine(hash, key); + } + return hash; + } + }; + + struct Equal + { + bool operator()(const DependencyGroup::Ptr& lhs, const DependencyGroup::Ptr& rhs) const + { + return std::equal( + lhs->m_Members.begin(), lhs->m_Members.end(), + rhs->m_Members.begin(), rhs->m_Members.end(), + [](const auto& l, const auto& r) { return l.first == r.first; } + ); + } + }; private: mutable std::mutex m_Mutex; @@ -193,25 +213,7 @@ private: String m_RedundancyGroupName; MembersMap m_Members; - using RegistryType = boost::multi_index_container< - DependencyGroup*, // The type of the elements stored in the container. - boost::multi_index::indexed_by< - // This unique index allows to search/erase dependency groups by their composite key in an efficient manner. - boost::multi_index::hashed_unique< - boost::multi_index::mem_fun, - std::hash - >, - // This non-unique index allows to search for dependency groups by their name, and reduces the overall - // runtime complexity. Without this index, we would have to iterate over all elements to find the one - // with the desired members and since containers don't allow erasing elements while iterating, we would - // have to copy each of them to a temporary container, and then erase and reinsert them back to the original - // container. This produces way too much overhead, and slows down the startup time of Icinga 2 significantly. - boost::multi_index::hashed_non_unique< - boost::multi_index::const_mem_fun, - std::hash - > - > - >; + using RegistryType = std::unordered_set; // The global registry of dependency groups. static std::mutex m_RegistryMutex; From 67a4889945de730965c391cf1136703dcc901c2e Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 7 Feb 2025 12:22:51 +0100 Subject: [PATCH 216/415] Checkable: Delay dependency group global registration on startup --- lib/icinga/checkable-dependency.cpp | 35 ++++++++++++++++++++++++++++- lib/icinga/checkable.cpp | 2 ++ lib/icinga/checkable.hpp | 2 ++ lib/icinga/dependency.hpp | 6 ++--- 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/lib/icinga/checkable-dependency.cpp b/lib/icinga/checkable-dependency.cpp index 21cbcd71f..47147bd36 100644 --- a/lib/icinga/checkable-dependency.cpp +++ b/lib/icinga/checkable-dependency.cpp @@ -3,7 +3,6 @@ #include "icinga/service.hpp" #include "icinga/dependency.hpp" #include "base/logger.hpp" -#include using namespace icinga; @@ -15,9 +14,34 @@ using namespace icinga; */ static constexpr int l_MaxDependencyRecursionLevel(256); +/** + * Register all the dependency groups of the current Checkable to the global dependency group registry. + * + * Initially, each Checkable object tracks locally its own dependency groups on Icinga 2 startup, and once the start + * signal of that Checkable is emitted, it pushes all the local tracked dependency groups to the global registry. + * Once the global registry is populated with all the local dependency groups, this Checkable may not necessarily + * contain the exact same dependency groups as it did before, as identical groups are merged together in the registry, + * but it's guaranteed to have the same *number* of dependency groups as before. + */ +void Checkable::PushDependencyGroupsToRegistry() +{ + std::lock_guard lock(m_DependencyMutex); + if (!m_DependencyGroupsPushedToRegistry) { + m_DependencyGroupsPushedToRegistry = true; + + decltype(m_DependencyGroups) dependencyGroups; + m_DependencyGroups.swap(dependencyGroups); + + for (auto& [dependencyGroupKey, dependencyGroup] : dependencyGroups) { + m_DependencyGroups.emplace(dependencyGroupKey, DependencyGroup::Register(dependencyGroup)); + } + } +} + std::vector Checkable::GetDependencyGroups() const { std::lock_guard lock(m_DependencyMutex); + std::vector dependencyGroups; for (const auto& [_, dependencyGroup] : m_DependencyGroups) { dependencyGroups.emplace_back(dependencyGroup); @@ -54,6 +78,15 @@ void Checkable::AddDependency(const Dependency::Ptr& dependency) std::lock_guard lock(m_DependencyMutex); auto dependencyGroupKey(GetDependencyGroupKey(dependency)); + if (!m_DependencyGroupsPushedToRegistry) { + auto& dependencyGroup = m_DependencyGroups[dependencyGroupKey]; + if (!dependencyGroup) { + dependencyGroup = new DependencyGroup(dependency->GetRedundancyGroup()); + } + dependencyGroup->AddDependency(dependency); + return; + } + std::set dependencies; if (auto it(m_DependencyGroups.find(dependencyGroupKey)); it != m_DependencyGroups.end()) { dependencies = DependencyGroup::Unregister(it->second, this); diff --git a/lib/icinga/checkable.cpp b/lib/icinga/checkable.cpp index ddf84cd1f..13fd778a3 100644 --- a/lib/icinga/checkable.cpp +++ b/lib/icinga/checkable.cpp @@ -80,6 +80,8 @@ void Checkable::OnAllConfigLoaded() void Checkable::Start(bool runtimeCreated) { + PushDependencyGroupsToRegistry(); + double now = Utility::GetTime(); { diff --git a/lib/icinga/checkable.hpp b/lib/icinga/checkable.hpp index 2d29bbec0..c3ac29612 100644 --- a/lib/icinga/checkable.hpp +++ b/lib/icinga/checkable.hpp @@ -186,6 +186,7 @@ public: bool IsFlapping() const; /* Dependencies */ + void PushDependencyGroupsToRegistry(); std::vector> GetDependencyGroups() const; void AddDependency(const intrusive_ptr& dependency); void RemoveDependency(const intrusive_ptr& dependency); @@ -250,6 +251,7 @@ private: /* Dependencies */ mutable std::mutex m_DependencyMutex; + bool m_DependencyGroupsPushedToRegistry{false}; std::map, intrusive_ptr> m_DependencyGroups; std::set > m_ReverseDependencies; diff --git a/lib/icinga/dependency.hpp b/lib/icinga/dependency.hpp index 0698a3e0a..0f3c257ee 100644 --- a/lib/icinga/dependency.hpp +++ b/lib/icinga/dependency.hpp @@ -152,6 +152,8 @@ public: } bool IsEmpty() const; + void AddDependency(const Dependency::Ptr& dependency); + void RemoveDependency(const Dependency::Ptr& dependency); std::vector GetDependenciesForChild(const Checkable* child) const; size_t GetDependenciesCount() const; @@ -169,9 +171,7 @@ public: State GetState(DependencyType dt = DependencyState, int rstack = 0) const; -protected: - void AddDependency(const Dependency::Ptr& dependency); - void RemoveDependency(const Dependency::Ptr& dependency); +private: void CopyDependenciesTo(const DependencyGroup::Ptr& dest); struct Hash From 6a0ec7013174f56c29742277c04e47e441d512ec Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 28 Jan 2025 16:14:55 +0100 Subject: [PATCH 217/415] Fix & adjust dependencies unittests --- test/icinga-dependencies.cpp | 143 +++++++++++++++++++++++++---------- 1 file changed, 104 insertions(+), 39 deletions(-) diff --git a/test/icinga-dependencies.cpp b/test/icinga-dependencies.cpp index e02385938..d9e7f651e 100644 --- a/test/icinga-dependencies.cpp +++ b/test/icinga-dependencies.cpp @@ -16,7 +16,7 @@ static Host::Ptr CreateHost(const std::string& name) return host; } -static Dependency::Ptr CreateDependency(Checkable::Ptr parent, Checkable::Ptr child, const std::string& name) +static Dependency::Ptr CreateDependency(Checkable::Ptr parent, Checkable::Ptr child, const String& name) { Dependency::Ptr dep = new Dependency(); dep->SetParent(parent); @@ -25,10 +25,10 @@ static Dependency::Ptr CreateDependency(Checkable::Ptr parent, Checkable::Ptr ch return dep; } -static void RegisterDependency(Dependency::Ptr dep, const std::string& redundancyGroup) +static void RegisterDependency(Dependency::Ptr dep, const String& redundancyGroup) { dep->SetRedundancyGroup(redundancyGroup); - DependencyGroup::Register(dep); + dep->GetChild()->AddDependency(dep); dep->GetParent()->AddReverseDependency(dep); } @@ -42,17 +42,29 @@ static void AssertCheckableRedundancyGroup(Checkable::Ptr checkable, int depende auto dependencyGroups(checkable->GetDependencyGroups()); BOOST_CHECK_MESSAGE( groupCount == dependencyGroups.size(), - "Dependency group count mismatch for '" << checkable->GetName() << "'" << " - expected=" << groupCount + "Dependency group count mismatch for '" << checkable->GetName() << "' - expected=" << groupCount << "; got=" << dependencyGroups.size() ); - if (groupCount > 0) { - BOOST_REQUIRE_MESSAGE(1 <= dependencyGroups.size(), "Checkable '" << checkable->GetName() << "' should have at least one dependency group."); + + for (auto& dependencyGroup : dependencyGroups) { BOOST_CHECK_MESSAGE( - totalDependenciesCount == dependencyGroups.begin()->get()->GetDependenciesCount(), - "Member count mismatch for '" << checkable->GetName() << "'" << " - expected=" << totalDependenciesCount - << "; got=" << dependencyGroups.begin()->get()->GetDependenciesCount() + totalDependenciesCount == dependencyGroup->GetDependenciesCount(), + "Dependency group '" << dependencyGroup->GetRedundancyGroupName() << "' and Checkable '" << checkable->GetName() + << "' total dependencies count mismatch - expected=" << totalDependenciesCount << "; got=" + << dependencyGroup->GetDependenciesCount() ); } + + if (groupCount > 0) { + BOOST_REQUIRE_MESSAGE(!dependencyGroups.empty(), "Checkable '" << checkable->GetName() << "' should have at least one dependency group."); + } +} + +static std::vector ExtractGroups(const Checkable::Ptr& checkable) +{ + auto dependencyGroups(checkable->GetDependencyGroups()); + std::sort(dependencyGroups.begin(), dependencyGroups.end()); + return dependencyGroups; } BOOST_AUTO_TEST_CASE(multi_parent) @@ -106,19 +118,19 @@ BOOST_AUTO_TEST_CASE(multi_parent) // It should still be unreachable, due to the duplicated dependency object above with ignore_soft_states set to false. BOOST_CHECK(childHost->IsReachable() == false); parentHost1->SetStateType(StateTypeHard); - DependencyGroup::Unregister(duplicateDep); + childHost->RemoveDependency(duplicateDep); /* The only DNS server is DOWN. * Expected result: childHost is unreachable. */ - DependencyGroup::Unregister(dep1); // Remove the dep and re-add it with a configured redundancy group. + childHost->RemoveDependency(dep1); // Remove the dep and re-add it with a configured redundancy group. RegisterDependency(dep1, "DNS"); BOOST_CHECK(childHost->IsReachable() == false); /* 1/2 DNS servers is DOWN. * Expected result: childHost is reachable. */ - DependencyGroup::Unregister(dep2); + childHost->RemoveDependency(dep2); RegisterDependency(dep2, "DNS"); BOOST_CHECK(childHost->IsReachable() == true); @@ -132,7 +144,7 @@ BOOST_AUTO_TEST_CASE(multi_parent) RegisterDependency(dep3, ""); // The grandparent is DOWN but the DNS redundancy group has to be still reachable. BOOST_CHECK_EQUAL(true, childHost->IsReachable()); - DependencyGroup::Unregister(dep3); + childHost->RemoveDependency(dep3); /* Both DNS servers are DOWN. * Expected result: childHost is unreachable. @@ -153,29 +165,72 @@ BOOST_AUTO_TEST_CASE(default_redundancy_group_registration_unregistration) Dependency::Ptr depCB(CreateDependency(CreateHost("B"), childHostC, "depCB")); RegisterDependency(depCB, ""); - AssertCheckableRedundancyGroup(childHostC, 2, 1, 2); - BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize()); + AssertCheckableRedundancyGroup(childHostC, 2, 2, 1); + BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize()); Checkable::Ptr childHostD(CreateHost("D")); Dependency::Ptr depDA(CreateDependency(depCA->GetParent(), childHostD, "depDA")); RegisterDependency(depDA, ""); - AssertCheckableRedundancyGroup(childHostD, 1, 1, 1); + AssertCheckableRedundancyGroup(childHostD, 1, 1, 2); BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize()); Dependency::Ptr depDB(CreateDependency(depCB->GetParent(), childHostD, "depDB")); RegisterDependency(depDB, ""); - AssertCheckableRedundancyGroup(childHostD, 2, 1, 4); - AssertCheckableRedundancyGroup(childHostC, 2, 1, 4); - BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize()); + AssertCheckableRedundancyGroup(childHostD, 2, 2, 2); + AssertCheckableRedundancyGroup(childHostC, 2, 2, 2); + BOOST_TEST(ExtractGroups(childHostC) == ExtractGroups(childHostD), boost::test_tools::per_element()); + BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize()); - DependencyGroup::Unregister(depCA); - DependencyGroup::Unregister(depDA); + // This is an exact duplicate of depCA, but with a different dependency name. + Dependency::Ptr depCA2(CreateDependency(depCA->GetParent(), childHostC, "depCA2")); + // This is a duplicate of depCA, but with a different state filter. + Dependency::Ptr depCA3(CreateDependency(depCA->GetParent(), childHostC, "depCA3")); + depCA3->SetStateFilter(StateFilterUp, true); + // This is a duplicate of depCA, but with a different ignore_soft_states flag. + Dependency::Ptr depCA4(CreateDependency(depCA->GetParent(), childHostC, "depCA4")); + depCA4->SetIgnoreSoftStates(false, true); + + for (auto& dependency : {depCA2, depCA3, depCA4}) { + bool isAnExactDuplicate = dependency == depCA2; + RegisterDependency(dependency, ""); + + if (isAnExactDuplicate) { + BOOST_TEST(ExtractGroups(childHostC) == ExtractGroups(childHostD), boost::test_tools::per_element()); + } + + for (auto& dependencyGroup : childHostD->GetDependencyGroups()) { + if (dependency->GetParent() == dependencyGroup->GetDependenciesForChild(childHostD.get()).front()->GetParent()) { + BOOST_CHECK_EQUAL(isAnExactDuplicate ? 3 : 1, dependencyGroup->GetDependenciesCount()); + } else { + BOOST_CHECK_EQUAL(2, dependencyGroup->GetDependenciesCount()); + } + BOOST_CHECK_EQUAL(2, childHostD->GetDependencies().size()); + } + + for (auto& dependencyGroup : childHostC->GetDependencyGroups()) { + if (dependency->GetParent() == dependencyGroup->GetDependenciesForChild(childHostC.get()).front()->GetParent()) { + // If depCA2 is currently being processed, then the group should have 3 dependencies, that's because + // depCA2 is an exact duplicate of depCA, and depCA shares the same group with depDA. + BOOST_CHECK_EQUAL(isAnExactDuplicate ? 3 : 2, dependencyGroup->GetDependenciesCount()); + } else { + BOOST_CHECK_EQUAL(2, dependencyGroup->GetDependenciesCount()); + } + // The 3 dependencies are depCA, depCB, and the current one from the loop. + BOOST_CHECK_EQUAL(3, childHostC->GetDependencies().size()); + } + BOOST_CHECK_EQUAL(isAnExactDuplicate ? 2 : 3, DependencyGroup::GetRegistrySize()); + childHostC->RemoveDependency(dependency); + } + + childHostC->RemoveDependency(depCA); + childHostD->RemoveDependency(depDA); AssertCheckableRedundancyGroup(childHostC, 1, 1, 2); AssertCheckableRedundancyGroup(childHostD, 1, 1, 2); + BOOST_TEST(ExtractGroups(childHostC) == ExtractGroups(childHostD), boost::test_tools::per_element()); BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize()); - DependencyGroup::Unregister(depCB); - DependencyGroup::Unregister(depDB); + childHostC->RemoveDependency(depCB); + childHostD->RemoveDependency(depDB); AssertCheckableRedundancyGroup(childHostC, 0, 0, 0); AssertCheckableRedundancyGroup(childHostD, 0, 0, 0); BOOST_CHECK_EQUAL(0, DependencyGroup::GetRegistrySize()); @@ -206,31 +261,33 @@ BOOST_AUTO_TEST_CASE(simple_redundancy_group_registration_unregistration) // Still 1 redundancy group, but there should be 4 dependencies now, i.e. 2 for each child Checkable. AssertCheckableRedundancyGroup(childHostC, 2, 1, 4); AssertCheckableRedundancyGroup(childHostD, 2, 1, 4); + BOOST_TEST(ExtractGroups(childHostC) == ExtractGroups(childHostD), boost::test_tools::per_element()); BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize()); - DependencyGroup::Unregister(depCA); + childHostC->RemoveDependency(depCA); // After unregistering depCA, childHostC should have a new redundancy group with only depCB as dependency, and... AssertCheckableRedundancyGroup(childHostC, 1, 1, 1); // ...childHostD should still have the same redundancy group as before but also with only two dependencies. AssertCheckableRedundancyGroup(childHostD, 2, 1, 2); BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize()); - DependencyGroup::Unregister(depDA); + childHostD->RemoveDependency(depDA); // Nothing should have changed for childHostC, but childHostD should now have a fewer group dependency, i.e. // both child hosts should have the same redundancy group with only depCB and depDB as dependency. AssertCheckableRedundancyGroup(childHostC, 1, 1, 2); AssertCheckableRedundancyGroup(childHostD, 1, 1, 2); + BOOST_TEST(ExtractGroups(childHostC) == ExtractGroups(childHostD), boost::test_tools::per_element()); BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize()); - DependencyGroup::Register(depDA); - DependencyGroup::Unregister(depDB); + RegisterDependency(depDA, depDA->GetRedundancyGroup()); + childHostD->RemoveDependency(depDB); // Nothing should have changed for childHostC, but both should now have a separate group with only depCB and depDA as dependency. AssertCheckableRedundancyGroup(childHostC, 1, 1, 1); AssertCheckableRedundancyGroup(childHostD, 1, 1, 1); BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize()); - DependencyGroup::Unregister(depCB); - DependencyGroup::Unregister(depDA); + childHostC->RemoveDependency(depCB); + childHostD->RemoveDependency(depDA); AssertCheckableRedundancyGroup(childHostC, 0, 0, 0); AssertCheckableRedundancyGroup(childHostD, 0, 0, 0); BOOST_CHECK_EQUAL(0, DependencyGroup::GetRegistrySize()); @@ -248,6 +305,7 @@ BOOST_AUTO_TEST_CASE(mixed_redundancy_group_registration_unregsitration) Dependency::Ptr depDA(CreateDependency(depCA->GetParent(), childHostD, "depDA")); RegisterDependency(depDA, "redundant"); AssertCheckableRedundancyGroup(childHostD, 1, 1, 2); + BOOST_TEST(ExtractGroups(childHostC) == ExtractGroups(childHostD), boost::test_tools::per_element()); BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize()); Dependency::Ptr depCB(CreateDependency(CreateHost("B"), childHostC, "depCB")); @@ -260,6 +318,7 @@ BOOST_AUTO_TEST_CASE(mixed_redundancy_group_registration_unregsitration) RegisterDependency(depDB, "redundant"); AssertCheckableRedundancyGroup(childHostC, 2, 1, 4); AssertCheckableRedundancyGroup(childHostD, 2, 1, 4); + BOOST_TEST(ExtractGroups(childHostC) == ExtractGroups(childHostD), boost::test_tools::per_element()); BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize()); Checkable::Ptr childHostE(CreateHost("childE")); @@ -274,6 +333,8 @@ BOOST_AUTO_TEST_CASE(mixed_redundancy_group_registration_unregsitration) AssertCheckableRedundancyGroup(childHostC, 2, 1, 6); AssertCheckableRedundancyGroup(childHostD, 2, 1, 6); AssertCheckableRedundancyGroup(childHostE, 2, 1, 6); + auto childHostCGroups(ExtractGroups(childHostC)); + BOOST_TEST((childHostCGroups == ExtractGroups(childHostD) && childHostCGroups == ExtractGroups(childHostE))); BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize()); Dependency::Ptr depEZ(CreateDependency(CreateHost("Z"), childHostE, "depEZ")); @@ -281,36 +342,40 @@ BOOST_AUTO_TEST_CASE(mixed_redundancy_group_registration_unregsitration) // Child host E should have a new redundancy group with 3 dependencies and the other two should still share the same group. AssertCheckableRedundancyGroup(childHostC, 2, 1, 4); AssertCheckableRedundancyGroup(childHostD, 2, 1, 4); + BOOST_TEST(ExtractGroups(childHostC) == ExtractGroups(childHostD), boost::test_tools::per_element()); AssertCheckableRedundancyGroup(childHostE, 3, 1, 3); BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize()); - DependencyGroup::Unregister(depEA); + childHostE->RemoveDependency(depEA); AssertCheckableRedundancyGroup(childHostC, 2, 1, 4); AssertCheckableRedundancyGroup(childHostD, 2, 1, 4); + BOOST_TEST(ExtractGroups(childHostC) == ExtractGroups(childHostD), boost::test_tools::per_element()); AssertCheckableRedundancyGroup(childHostE, 2, 1, 2); BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize()); - DependencyGroup::Register(depEA); // Re-register depEA and instead... - DependencyGroup::Unregister(depEZ); // ...unregister depEZ and check if all the hosts share the same group again. + RegisterDependency(depEA, depEA->GetRedundancyGroup()); // Re-register depEA and instead... + childHostE->RemoveDependency(depEZ); // ...unregister depEZ and check if all the hosts share the same group again. // All 3 hosts share the same group again, and each host has 2 dependencies, thus 6 dependencies in total. AssertCheckableRedundancyGroup(childHostC, 2, 1, 6); AssertCheckableRedundancyGroup(childHostD, 2, 1, 6); AssertCheckableRedundancyGroup(childHostE, 2, 1, 6); + childHostCGroups = ExtractGroups(childHostC); + BOOST_TEST((childHostCGroups == ExtractGroups(childHostD) && childHostCGroups == ExtractGroups(childHostE))); BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize()); - DependencyGroup::Unregister(depCA); - DependencyGroup::Unregister(depDB); - DependencyGroup::Unregister(depEB); + childHostC->RemoveDependency(depCA); + childHostD->RemoveDependency(depDB); + childHostE->RemoveDependency(depEB); // Child host C has now a separate group with only depCB as dependency, and child hosts D and E share the same group. AssertCheckableRedundancyGroup(childHostC, 1, 1, 1); AssertCheckableRedundancyGroup(childHostD, 1, 1, 2); AssertCheckableRedundancyGroup(childHostE, 1, 1, 2); - // Child host C has now a separate group with only depCB as member, and child hosts D and E share the same group. + BOOST_TEST(ExtractGroups(childHostD) == ExtractGroups(childHostE), boost::test_tools::per_element()); BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize()); - DependencyGroup::Unregister(depCB); - DependencyGroup::Unregister(depDA); - DependencyGroup::Unregister(depEA); + childHostC->RemoveDependency(depCB); + childHostD->RemoveDependency(depDA); + childHostE->RemoveDependency(depEA); AssertCheckableRedundancyGroup(childHostC, 0, 0, 0); AssertCheckableRedundancyGroup(childHostD, 0, 0, 0); AssertCheckableRedundancyGroup(childHostE, 0, 0, 0); From b462028b4f809427a8878f8b8b643e19b1f3b1e4 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 28 Jan 2025 16:30:00 +0100 Subject: [PATCH 218/415] Add basic unittests for bulk group registration --- test/CMakeLists.txt | 1 + test/icinga-dependencies.cpp | 52 +++++++++++++++++++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 7857c1261..810b35bef 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -232,6 +232,7 @@ add_boost_test(base icinga_checkresult/service_flapping_notification icinga_checkresult/suppressed_notification icinga_dependencies/multi_parent + icinga_dependencies/push_dependency_groups_to_registry icinga_dependencies/default_redundancy_group_registration_unregistration icinga_dependencies/simple_redundancy_group_registration_unregistration icinga_dependencies/mixed_redundancy_group_registration_unregsitration diff --git a/test/icinga-dependencies.cpp b/test/icinga-dependencies.cpp index d9e7f651e..b1fbf7b4c 100644 --- a/test/icinga-dependencies.cpp +++ b/test/icinga-dependencies.cpp @@ -9,10 +9,13 @@ using namespace icinga; BOOST_AUTO_TEST_SUITE(icinga_dependencies) -static Host::Ptr CreateHost(const std::string& name) +static Host::Ptr CreateHost(const std::string& name, bool pushDependencyGroupsToRegistry = true) { Host::Ptr host = new Host(); host->SetName(name); + if (pushDependencyGroupsToRegistry) { + host->PushDependencyGroupsToRegistry(); + } return host; } @@ -155,6 +158,53 @@ BOOST_AUTO_TEST_CASE(multi_parent) BOOST_CHECK(childHost->IsReachable() == false); } +BOOST_AUTO_TEST_CASE(push_dependency_groups_to_registry) +{ + Checkable::Ptr childHostC(CreateHost("C", false)); + Checkable::Ptr childHostD(CreateHost("D", false)); + std::set dependencies; // Keep track of all dependencies to avoid unexpected deletions. + for (auto& parent : {String("A"), String("B"), String("E")}) { + Dependency::Ptr depC(CreateDependency(CreateHost(parent), childHostC, "depC" + parent)); + Dependency::Ptr depD(CreateDependency(depC->GetParent(), childHostD, "depD" + parent)); + if (parent == "A") { + Dependency::Ptr depCA2(CreateDependency(depC->GetParent(), childHostC, "depCA2")); + childHostC->AddDependency(depCA2); + dependencies.emplace(depCA2); + } else { + depC->SetRedundancyGroup("redundant", true); + depD->SetRedundancyGroup("redundant", true); + + if (parent == "B") { // Create an exact duplicate of depC, but with a different name. + Dependency::Ptr depCB2(CreateDependency(depC->GetParent(), childHostC, "depCB2")); + depCB2->SetRedundancyGroup("redundant", true); + childHostC->AddDependency(depCB2); + dependencies.emplace(depCB2); + } + } + childHostC->AddDependency(depC); + childHostD->AddDependency(depD); + dependencies.insert({depC, depD}); + } + + childHostC->PushDependencyGroupsToRegistry(); + childHostD->PushDependencyGroupsToRegistry(); + + BOOST_TEST(ExtractGroups(childHostC) == ExtractGroups(childHostD), boost::test_tools::per_element()); + BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize()); + for (auto& checkable : {childHostC, childHostD}) { + BOOST_CHECK_EQUAL(2, checkable->GetDependencyGroups().size()); + for (auto& dependencyGroup : checkable->GetDependencyGroups()) { + if (dependencyGroup->IsRedundancyGroup()) { + BOOST_CHECK_EQUAL(5, dependencyGroup->GetDependenciesCount()); + BOOST_CHECK_EQUAL(checkable == childHostC ? 5 : 3, checkable->GetDependencies().size()); + } else { + BOOST_CHECK_EQUAL(3, dependencyGroup->GetDependenciesCount()); + BOOST_CHECK_EQUAL(checkable == childHostC ? 5 : 3, checkable->GetDependencies().size()); + } + } + } +} + BOOST_AUTO_TEST_CASE(default_redundancy_group_registration_unregistration) { Checkable::Ptr childHostC(CreateHost("C")); From 806fff950c290af375931abb74ee7a76d345a837 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 7 Feb 2025 16:51:00 +0100 Subject: [PATCH 219/415] Checkable: Emit boost signals when changing dependency groups at runtime --- lib/icinga/checkable-dependency.cpp | 49 +++++++++++++++++++++-------- lib/icinga/checkable.hpp | 2 +- lib/icinga/dependency-group.cpp | 16 ++++++---- lib/icinga/dependency.cpp | 2 +- lib/icinga/dependency.hpp | 5 ++- 5 files changed, 52 insertions(+), 22 deletions(-) diff --git a/lib/icinga/checkable-dependency.cpp b/lib/icinga/checkable-dependency.cpp index 47147bd36..51e054e60 100644 --- a/lib/icinga/checkable-dependency.cpp +++ b/lib/icinga/checkable-dependency.cpp @@ -75,7 +75,7 @@ static std::variant GetDependencyGroupKey(const Dependency:: */ void Checkable::AddDependency(const Dependency::Ptr& dependency) { - std::lock_guard lock(m_DependencyMutex); + std::unique_lock lock(m_DependencyMutex); auto dependencyGroupKey(GetDependencyGroupKey(dependency)); if (!m_DependencyGroupsPushedToRegistry) { @@ -88,27 +88,38 @@ void Checkable::AddDependency(const Dependency::Ptr& dependency) } std::set dependencies; + bool removeGroup(false); + + DependencyGroup::Ptr existingGroup; if (auto it(m_DependencyGroups.find(dependencyGroupKey)); it != m_DependencyGroups.end()) { - dependencies = DependencyGroup::Unregister(it->second, this); + existingGroup = it->second; + std::tie(dependencies, removeGroup) = DependencyGroup::Unregister(existingGroup, this); m_DependencyGroups.erase(it); } dependencies.emplace(dependency); - m_DependencyGroups.emplace( - dependencyGroupKey, - DependencyGroup::Register(new DependencyGroup(dependency->GetRedundancyGroup(), dependencies)) - ); + auto dependencyGroup(DependencyGroup::Register(new DependencyGroup(dependency->GetRedundancyGroup(), dependencies))); + m_DependencyGroups.emplace(dependencyGroupKey, dependencyGroup); + + lock.unlock(); + + if (existingGroup) { + dependencies.erase(dependency); + DependencyGroup::OnChildRemoved(existingGroup, {dependencies.begin(), dependencies.end()}, removeGroup); + } + DependencyGroup::OnChildRegistered(this, dependencyGroup); } /** * Remove the provided dependency from the current Checkable list of dependencies. * * @param dependency The dependency to remove. + * @param runtimeRemoved Whether the given dependency object is being removed at runtime. */ -void Checkable::RemoveDependency(const Dependency::Ptr& dependency) +void Checkable::RemoveDependency(const Dependency::Ptr& dependency, bool runtimeRemoved) { - std::lock_guard lock(m_DependencyMutex); + std::unique_lock lock(m_DependencyMutex); auto dependencyGroupKey(GetDependencyGroupKey(dependency)); auto it = m_DependencyGroups.find(dependencyGroupKey); @@ -116,15 +127,27 @@ void Checkable::RemoveDependency(const Dependency::Ptr& dependency) return; } - std::set dependencies(DependencyGroup::Unregister(it->second, this)); + DependencyGroup::Ptr existingGroup(it->second); + auto [dependencies, removeGroup] = DependencyGroup::Unregister(existingGroup, this); + m_DependencyGroups.erase(it); dependencies.erase(dependency); + DependencyGroup::Ptr newDependencyGroup; if (!dependencies.empty()) { - m_DependencyGroups.emplace( - dependencyGroupKey, - DependencyGroup::Register(new DependencyGroup(dependency->GetRedundancyGroup(), dependencies)) - ); + newDependencyGroup = DependencyGroup::Register(new DependencyGroup(dependency->GetRedundancyGroup(), dependencies)); + m_DependencyGroups.emplace(dependencyGroupKey, newDependencyGroup); + } + + lock.unlock(); + + if (runtimeRemoved) { + dependencies.emplace(dependency); + DependencyGroup::OnChildRemoved(existingGroup, {dependencies.begin(), dependencies.end()}, removeGroup); + + if (newDependencyGroup) { + DependencyGroup::OnChildRegistered(this, newDependencyGroup); + } } } diff --git a/lib/icinga/checkable.hpp b/lib/icinga/checkable.hpp index c3ac29612..53db79d72 100644 --- a/lib/icinga/checkable.hpp +++ b/lib/icinga/checkable.hpp @@ -189,7 +189,7 @@ public: void PushDependencyGroupsToRegistry(); std::vector> GetDependencyGroups() const; void AddDependency(const intrusive_ptr& dependency); - void RemoveDependency(const intrusive_ptr& dependency); + void RemoveDependency(const intrusive_ptr& dependency, bool runtimeRemoved = false); std::vector > GetDependencies() const; bool HasAnyDependencies() const; diff --git a/lib/icinga/dependency-group.cpp b/lib/icinga/dependency-group.cpp index eabe69a0b..47e43316f 100644 --- a/lib/icinga/dependency-group.cpp +++ b/lib/icinga/dependency-group.cpp @@ -5,6 +5,9 @@ using namespace icinga; +boost::signals2::signal DependencyGroup::OnChildRegistered; +boost::signals2::signal&, bool)> DependencyGroup::OnChildRemoved; + std::mutex DependencyGroup::m_RegistryMutex; DependencyGroup::RegistryType DependencyGroup::m_Registry; @@ -36,15 +39,15 @@ DependencyGroup::Ptr DependencyGroup::Register(const DependencyGroup::Ptr& depen * @param dependencyGroup The dependency group to unregister the child Checkable from. * @param child The child Checkable to detach from the dependency group. * - * @return - Returns the dependency objects of the child Checkable that were member of the provided dependency group. + * @return - Returns the dependency objects of the child Checkable that were member of the provided dependency group + * and a boolean indicating whether the dependency group has been erased from the global registry. */ -std::set DependencyGroup::Unregister(const DependencyGroup::Ptr& dependencyGroup, const Checkable::Ptr& child) +std::pair, bool> DependencyGroup::Unregister(const DependencyGroup::Ptr& dependencyGroup, const Checkable::Ptr& child) { std::lock_guard lock(m_RegistryMutex); - std::vector dependencies; if (auto it(m_Registry.find(dependencyGroup)); it != m_Registry.end()) { - const auto& existingGroup(*it); - dependencies = existingGroup->GetDependenciesForChild(child.get()); + auto existingGroup(*it); + auto dependencies(existingGroup->GetDependenciesForChild(child.get())); for (const auto& dependency : dependencies) { existingGroup->RemoveDependency(dependency); @@ -53,8 +56,9 @@ std::set DependencyGroup::Unregister(const DependencyGroup::Ptr if (existingGroup->IsEmpty()) { m_Registry.erase(it); } + return {{dependencies.begin(), dependencies.end()}, existingGroup->IsEmpty()}; } - return {dependencies.begin(), dependencies.end()}; + return {{}, false}; } /** diff --git a/lib/icinga/dependency.cpp b/lib/icinga/dependency.cpp index a9a7bf372..c45015e8f 100644 --- a/lib/icinga/dependency.cpp +++ b/lib/icinga/dependency.cpp @@ -259,7 +259,7 @@ void Dependency::Stop(bool runtimeRemoved) { ObjectImpl::Stop(runtimeRemoved); - GetChild()->RemoveDependency(this); + GetChild()->RemoveDependency(this, runtimeRemoved); GetParent()->RemoveReverseDependency(this); } diff --git a/lib/icinga/dependency.hpp b/lib/icinga/dependency.hpp index 0f3c257ee..a46b785c4 100644 --- a/lib/icinga/dependency.hpp +++ b/lib/icinga/dependency.hpp @@ -136,7 +136,7 @@ public: DependencyGroup(String name, const std::set& dependencies); static DependencyGroup::Ptr Register(const DependencyGroup::Ptr& dependencyGroup); - static std::set Unregister(const DependencyGroup::Ptr& dependencyGroup, const Checkable::Ptr& child); + static std::pair, bool> Unregister(const DependencyGroup::Ptr& dependencyGroup, const Checkable::Ptr& child); static size_t GetRegistrySize(); static CompositeKeyType MakeCompositeKeyFor(const Dependency::Ptr& dependency); @@ -171,6 +171,9 @@ public: State GetState(DependencyType dt = DependencyState, int rstack = 0) const; + static boost::signals2::signal OnChildRegistered; + static boost::signals2::signal&, bool)> OnChildRemoved; + private: void CopyDependenciesTo(const DependencyGroup::Ptr& dest); From 8640a3f84e86dd6da1a8ca2ece0ac0e43ee2ad79 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 10 Feb 2025 08:30:32 +0100 Subject: [PATCH 220/415] Checkable: Extract parents directly from dependency groups --- lib/icinga/checkable-dependency.cpp | 8 ++------ lib/icinga/dependency-group.cpp | 14 ++++++++++++++ lib/icinga/dependency.hpp | 1 + 3 files changed, 17 insertions(+), 6 deletions(-) diff --git a/lib/icinga/checkable-dependency.cpp b/lib/icinga/checkable-dependency.cpp index 51e054e60..b82aebc88 100644 --- a/lib/icinga/checkable-dependency.cpp +++ b/lib/icinga/checkable-dependency.cpp @@ -250,12 +250,8 @@ bool Checkable::AffectsChildren() const std::set Checkable::GetParents() const { std::set parents; - - for (const Dependency::Ptr& dep : GetDependencies()) { - Checkable::Ptr parent = dep->GetParent(); - - if (parent && parent.get() != this) - parents.insert(parent); + for (auto& dependencyGroup : GetDependencyGroups()) { + dependencyGroup->LoadParents(parents); } return parents; diff --git a/lib/icinga/dependency-group.cpp b/lib/icinga/dependency-group.cpp index 47e43316f..01520467e 100644 --- a/lib/icinga/dependency-group.cpp +++ b/lib/icinga/dependency-group.cpp @@ -133,6 +133,20 @@ std::vector DependencyGroup::GetDependenciesForChild(const Chec return dependencies; } +/** + * Load all parent Checkables of the current dependency group. + * + * @param parents The set to load the parent Checkables into. + */ +void DependencyGroup::LoadParents(std::set& parents) const +{ + std::lock_guard lock(m_Mutex); + for (auto& [compositeKey, children] : m_Members) { + ASSERT(!children.empty()); // We should never have an empty map for any given key at any given time. + parents.insert(std::get<0>(compositeKey)); + } +} + /** * Retrieve the number of dependency objects in the current dependency group. * diff --git a/lib/icinga/dependency.hpp b/lib/icinga/dependency.hpp index a46b785c4..fe005dbb2 100644 --- a/lib/icinga/dependency.hpp +++ b/lib/icinga/dependency.hpp @@ -155,6 +155,7 @@ public: void AddDependency(const Dependency::Ptr& dependency); void RemoveDependency(const Dependency::Ptr& dependency); std::vector GetDependenciesForChild(const Checkable* child) const; + void LoadParents(std::set& parents) const; size_t GetDependenciesCount() const; void SetIcingaDBIdentifier(const String& identifier); From 915ea6427e5ffb6826942ca00e1dab1afb177a23 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 10 Feb 2025 08:33:29 +0100 Subject: [PATCH 221/415] Use `GetParents()` in `FireSppressedNotifications()` It's way efficient than accessing them through the dependency objects, plus we won't have any duplicates. --- lib/icinga/checkable-notification.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/icinga/checkable-notification.cpp b/lib/icinga/checkable-notification.cpp index 2a1150556..282b95d32 100644 --- a/lib/icinga/checkable-notification.cpp +++ b/lib/icinga/checkable-notification.cpp @@ -167,8 +167,7 @@ void Checkable::FireSuppressedNotifications() } } - for (auto& dep : GetDependencies()) { - auto parent (dep->GetParent()); + for (auto& parent : GetParents()) { ObjectLock oLock (parent); if (!parent->GetProblem() && parent->GetLastStateChange() >= threshold) { From 0ab50fd82d5cdf4e9068f8d4e466f42f970d2a7f Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 10 Feb 2025 08:40:29 +0100 Subject: [PATCH 222/415] IcingaDB: Process dependencies runtime updates --- lib/icingadb/icingadb-objects.cpp | 173 +++++++++++++++++++++++++++++- lib/icingadb/icingadb.hpp | 8 +- 2 files changed, 177 insertions(+), 4 deletions(-) diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index e407373fa..0a5e5eb0f 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -110,6 +110,9 @@ void IcingaDB::ConfigStaticInitialize() IcingaDB::VersionChangedHandler(object); }); + DependencyGroup::OnChildRegistered.connect(&IcingaDB::DependencyGroupChildRegisteredHandler); + DependencyGroup::OnChildRemoved.connect(&IcingaDB::DependencyGroupChildRemovedHandler); + /* downtime start */ Downtime::OnDowntimeTriggered.connect(&IcingaDB::DowntimeStartedHandler); /* fixed/flexible downtime end or remove */ @@ -1115,16 +1118,20 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S * - RedisKey::DependencyEdgeState: State information for (each) dependency edge. Multiple edges may share the * same state. * - * For initial dumps, it shouldn't be necessary to set the `runtimeUpdates` parameter. + * If the `onlyDependencyGroup` parameter is set, only dependencies from this group are processed. This is useful + * when only a specific dependency group should be processed, e.g. during runtime updates. For initial config dumps, + * it shouldn't be necessary to set the `runtimeUpdates` and `onlyDependencyGroup` parameters. * * @param checkable The checkable object to extract dependencies from. * @param hMSets The map of Redis HMSETs to insert the dependency data into. * @param runtimeUpdates If set, runtime updates are additionally added to this vector. + * @param onlyDependencyGroup If set, only process dependency objects from this group. */ void IcingaDB::InsertCheckableDependencies( const Checkable::Ptr& checkable, std::map& hMSets, - std::vector* runtimeUpdates + std::vector* runtimeUpdates, + const DependencyGroup::Ptr& onlyDependencyGroup ) { // Only generate a dependency node event if the Checkable is actually part of some dependency graph. @@ -1150,7 +1157,14 @@ void IcingaDB::InsertCheckableDependencies( } } - for (auto& dependencyGroup : checkable->GetDependencyGroups()) { + // If `onlyDependencyGroup` is provided, process the dependencies only from that group; otherwise, + // retrieve all the dependency groups that the Checkable object is part of. + std::vector dependencyGroups{onlyDependencyGroup}; + if (!onlyDependencyGroup) { + dependencyGroups = checkable->GetDependencyGroups(); + } + + for (auto& dependencyGroup : dependencyGroups) { String edgeFromNodeId(checkableId); bool syncSharedEdgeState(false); @@ -2800,6 +2814,98 @@ void IcingaDB::SendCustomVarsChanged(const ConfigObject::Ptr& object, const Dict } } +void IcingaDB::SendDependencyGroupChildRegistered(const Checkable::Ptr& child, const DependencyGroup::Ptr& dependencyGroup) +{ + if (!m_Rcon || !m_Rcon->IsConnected()) { + return; + } + + std::vector runtimeUpdates; + std::map hMSets; + InsertCheckableDependencies(child, hMSets, &runtimeUpdates, dependencyGroup); + ExecuteRedisTransaction(m_Rcon, hMSets, runtimeUpdates); + + UpdateState(child, StateUpdate::Full); + UpdateDependenciesState(child, dependencyGroup); + + std::set parents; + dependencyGroup->LoadParents(parents); + for (const auto& parent : parents) { + // The total_children and affects_children columns might now have different outcome, so update the parent + // Checkable as well. The grandparent Checkable may still have wrong numbers of total children, though it's not + // worth traversing the whole tree way up and sending config updates for each one of them, as the next Redis + // config dump is going to fix it anyway. + SendConfigUpdate(parent, true); + } +} + +void IcingaDB::SendDependencyGroupChildRemoved( + const DependencyGroup::Ptr& dependencyGroup, + const std::vector& dependencies, + bool removeGroup +) +{ + if (!m_Rcon || !m_Rcon->IsConnected() || dependencies.empty()) { + return; + } + + Checkable::Ptr child; + std::set detachedParents; + for (const auto& dependency : dependencies) { + child = dependency->GetChild(); // All dependencies have the same child. + const auto& parent(dependency->GetParent()); + if (auto [_, inserted] = detachedParents.insert(dependency->GetParent().get()); inserted) { + String edgeId; + if (dependencyGroup->IsRedundancyGroup()) { + // If the redundancy group has no members left, it's going to be removed as well, so we need to + // delete dependency edges from that group to the parent Checkables. + if (removeGroup) { + auto id(HashValue(new Array{dependencyGroup->GetIcingaDBIdentifier(), GetObjectIdentifier(parent)})); + DeleteRelationship(id, RedisKey::DependencyEdge); + DeleteState(id, RedisKey::DependencyEdgeState); + } + + // Remove the connection from the child Checkable to the redundancy group. + edgeId = HashValue(new Array{GetObjectIdentifier(child), dependencyGroup->GetIcingaDBIdentifier()}); + } else { + // Remove the edge between the parent and child Checkable linked through the removed dependency. + edgeId = HashValue(new Array{GetObjectIdentifier(child), GetObjectIdentifier(parent)}); + } + + DeleteRelationship(edgeId, RedisKey::DependencyEdge); + + // The total_children and affects_children columns might now have different outcome, so update the parent + // Checkable as well. The grandparent Checkable may still have wrong numbers of total children, though it's + // not worth traversing the whole tree way up and sending config updates for each one of them, as the next + // Redis config dump is going to fix it anyway. + SendConfigUpdate(parent, true); + + if (!parent->HasAnyDependencies()) { + // If the parent Checkable isn't part of any other dependency chain anymore, drop its dependency node entry. + DeleteRelationship(GetObjectIdentifier(parent), RedisKey::DependencyNode); + } + } + } + + if (removeGroup && dependencyGroup->IsRedundancyGroup()) { + String redundancyGroupId(dependencyGroup->GetIcingaDBIdentifier()); + DeleteRelationship(redundancyGroupId, RedisKey::DependencyNode); + DeleteRelationship(redundancyGroupId, RedisKey::RedundancyGroup); + + DeleteState(redundancyGroupId, RedisKey::RedundancyGroupState); + DeleteState(redundancyGroupId, RedisKey::DependencyEdgeState); + } else if (removeGroup) { + // Note: The Icinga DB identifier of a non-redundant dependency group is used as the edge state ID + // and shared by all of its dependency objects. See also SerializeDependencyEdgeState() for details. + DeleteState(dependencyGroup->GetIcingaDBIdentifier(), RedisKey::DependencyEdgeState); + } + + if (!child->HasAnyDependencies()) { + // If the child Checkable has no parent and reverse dependencies, we can safely remove the dependency node. + DeleteRelationship(GetObjectIdentifier(child), RedisKey::DependencyNode); + } +} + Dictionary::Ptr IcingaDB::SerializeState(const Checkable::Ptr& checkable) { Dictionary::Ptr attrs = new Dictionary(); @@ -3078,6 +3184,20 @@ void IcingaDB::NextCheckUpdatedHandler(const Checkable::Ptr& checkable) } } +void IcingaDB::DependencyGroupChildRegisteredHandler(const Checkable::Ptr& child, const DependencyGroup::Ptr& dependencyGroup) +{ + for (const auto& rw : ConfigType::GetObjectsByType()) { + rw->SendDependencyGroupChildRegistered(child, dependencyGroup); + } +} + +void IcingaDB::DependencyGroupChildRemovedHandler(const DependencyGroup::Ptr& dependencyGroup, const std::vector& dependencies, bool removeGroup) +{ + for (const auto& rw : ConfigType::GetObjectsByType()) { + rw->SendDependencyGroupChildRemoved(dependencyGroup, dependencies, removeGroup); + } +} + void IcingaDB::HostProblemChangedHandler(const Service::Ptr& service) { for (auto& rw : ConfigType::GetObjectsByType()) { /* Host state changes affect is_handled and severity of services. */ @@ -3196,6 +3316,53 @@ void IcingaDB::DeleteRelationship(const String& id, const String& redisKeyWithou m_Rcon->FireAndForgetQueries(queries, Prio::Config); } +void IcingaDB::DeleteRelationship(const String& id, RedisKey redisKey, bool hasChecksum) +{ + switch (redisKey) { + case RedisKey::RedundancyGroup: + DeleteRelationship(id, "redundancygroup", hasChecksum); + break; + case RedisKey::DependencyNode: + DeleteRelationship(id, "dependency:node", hasChecksum); + break; + case RedisKey::DependencyEdge: + DeleteRelationship(id, "dependency:edge", hasChecksum); + break; + default: + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid RedisKey provided")); + } +} + +void IcingaDB::DeleteState(const String& id, RedisKey redisKey, bool hasChecksum) const +{ + String redisKeyWithoutPrefix; + switch (redisKey) { + case RedisKey::RedundancyGroupState: + redisKeyWithoutPrefix = "redundancygroup:state"; + break; + case RedisKey::DependencyEdgeState: + redisKeyWithoutPrefix = "dependency:edge:state"; + break; + default: + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid state RedisKey provided")); + } + + Log(LogNotice, "IcingaDB") + << "Deleting state " << std::quoted(redisKeyWithoutPrefix.CStr()) << " -> " << std::quoted(id.CStr()); + + RedisConnection::Queries hdels; + if (hasChecksum) { + hdels.emplace_back(RedisConnection::Query{"HDEL", m_PrefixConfigCheckSum + redisKeyWithoutPrefix, id}); + } + hdels.emplace_back(RedisConnection::Query{"HDEL", m_PrefixConfigObject + redisKeyWithoutPrefix, id}); + + m_Rcon->FireAndForgetQueries(std::move(hdels), Prio::RuntimeStateSync); + m_Rcon->FireAndForgetQueries({{ + "XADD", "icinga:runtime:state", "MAXLEN", "~", "1000000", "*", + "redis_key", m_PrefixConfigObject + redisKeyWithoutPrefix, "id", id, "runtime_type", "delete" + }}, Prio::RuntimeStateStream, {0, 1}); +} + /** * Add the provided data to the Redis HMSETs map. * diff --git a/lib/icingadb/icingadb.hpp b/lib/icingadb/icingadb.hpp index d8f7843a5..df454e31a 100644 --- a/lib/icingadb/icingadb.hpp +++ b/lib/icingadb/icingadb.hpp @@ -111,7 +111,7 @@ private: std::vector GetTypeOverwriteKeys(const String& type); std::vector GetTypeDumpSignalKeys(const Type::Ptr& type); void InsertCheckableDependencies(const Checkable::Ptr& checkable, std::map& hMSets, - std::vector* runtimeUpdates); + std::vector* runtimeUpdates, const DependencyGroup::Ptr& onlyDependencyGroup = nullptr); void InsertObjectDependencies(const ConfigObject::Ptr& object, const String typeName, std::map>& hMSets, std::vector& runtimeUpdates, bool runtimeUpdate); void UpdateDependenciesState(const Checkable::Ptr& checkable, const DependencyGroup::Ptr& onlyDependencyGroup = nullptr) const; @@ -124,6 +124,8 @@ private: void AddObjectDataToRuntimeUpdates(std::vector& runtimeUpdates, const String& objectKey, const String& redisKey, const Dictionary::Ptr& data); void DeleteRelationship(const String& id, const String& redisKeyWithoutPrefix, bool hasChecksum = false); + void DeleteRelationship(const String& id, RedisKey redisKey, bool hasChecksum = false); + void DeleteState(const String& id, RedisKey redisKey, bool hasChecksum = false) const; void AddDataToHmSets(std::map& hMSets, RedisKey redisKey, const String& id, const Dictionary::Ptr& data) const; void SendSentNotification( @@ -149,6 +151,8 @@ private: void SendCommandEnvChanged(const ConfigObject::Ptr& command, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues); void SendCommandArgumentsChanged(const ConfigObject::Ptr& command, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues); void SendCustomVarsChanged(const ConfigObject::Ptr& object, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues); + void SendDependencyGroupChildRegistered(const Checkable::Ptr& child, const DependencyGroup::Ptr& dependencyGroup); + void SendDependencyGroupChildRemoved(const DependencyGroup::Ptr& dependencyGroup, const std::vector& dependencies, bool removeGroup); void ForwardHistoryEntries(); @@ -195,6 +199,8 @@ private: static void FlappingChangeHandler(const Checkable::Ptr& checkable, double changeTime); static void NewCheckResultHandler(const Checkable::Ptr& checkable); static void NextCheckUpdatedHandler(const Checkable::Ptr& checkable); + static void DependencyGroupChildRegisteredHandler(const Checkable::Ptr& child, const DependencyGroup::Ptr& dependencyGroup); + static void DependencyGroupChildRemovedHandler(const DependencyGroup::Ptr& dependencyGroup, const std::vector& dependencies, bool removeGroup); static void HostProblemChangedHandler(const Service::Ptr& service); static void AcknowledgementSetHandler(const Checkable::Ptr& checkable, const String& author, const String& comment, AcknowledgementType type, bool persistent, double changeTime, double expiry); static void AcknowledgementClearedHandler(const Checkable::Ptr& checkable, const String& removedBy, double changeTime); From ce1ed8556c8a7c8ad693c197c3665cc5a2fbc99b Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Wed, 12 Feb 2025 11:18:12 +0100 Subject: [PATCH 223/415] Simplify DependencyGroup::GetState() implementation The new implementation just counts reachable and available parents and determines the overall result by comparing numbers, see inline comments for more information. This also fixes an issue in the previous implementation: if it didn't return early from the loop, it would just return the state of the last parent considered which may not actually represent the group state accurately. --- lib/icinga/dependency-group.cpp | 42 ++++++++++++++++++++------------- lib/icinga/dependency.hpp | 6 ++++- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/lib/icinga/dependency-group.cpp b/lib/icinga/dependency-group.cpp index 01520467e..043e8f514 100644 --- a/lib/icinga/dependency-group.cpp +++ b/lib/icinga/dependency-group.cpp @@ -322,26 +322,34 @@ DependencyGroup::State DependencyGroup::GetState(DependencyType dt, int rstack) members = m_Members; } - State state{false /* Reachable */, false /* OK */}; - for (auto& [_, children] : members) { - for (auto& [checkable, dependency] : children) { - state.Reachable = dependency->GetParent()->IsReachable(dt, rstack); - if (!state.Reachable && !IsRedundancyGroup()) { - return state; - } + size_t reachable = 0, available = 0; - if (state.Reachable) { - state.OK = dependency->IsAvailable(dt); - // If this is a redundancy group, and we have found one functional path, that's enough and we can return. - // Likewise, if this is a non-redundant dependency group, and we have found one non-functional path, - // we have to mark the group as failed and return. - if (state.OK == IsRedundancyGroup()) { // OK && IsRedundancyGroup() || !OK && !IsRedundancyGroup() - return state; - } + for (auto& [_, dependencies] : members) { + ASSERT(!dependencies.empty()); + + // Dependencies are grouped by parent and all config attributes affecting their availability. Hence, only + // one dependency from the map entry has to be considered, all others will share the same state. + if (auto& [_, dependency] = *dependencies.begin(); dependency->GetParent()->IsReachable(dt, rstack)) { + reachable++; + + // Only reachable parents are considered for availability. If they are unreachable and checks are disabled, + // they could be incorrectly treated as available otherwise. + if (dependency->IsAvailable(dt)) { + available++; } - break; // Move on to the next batch of group members (next composite key). } } - return state; + if (IsRedundancyGroup()) { + // The state of a redundancy group is determined by the best state of any parent. If any parent ist reachable, + // the redundancy group is reachable, analogously for availability. Note that an unreachable group cannot be + // available as reachable = 0 implies available = 0. + return {reachable > 0, available > 0}; + } else { + // For dependencies without a redundancy group, members.size() will be 1 in almost all cases. It will only + // contain more elements if there are duplicate dependency config objects between two checkables. In this case, + // all of them have to be reachable or available as they don't provide redundancy. Note that unreachable implies + // unavailable here as well as only reachable parents count towards the number of available parents. + return {reachable >= members.size(), available == members.size()}; + } } diff --git a/lib/icinga/dependency.hpp b/lib/icinga/dependency.hpp index fe005dbb2..49fb93989 100644 --- a/lib/icinga/dependency.hpp +++ b/lib/icinga/dependency.hpp @@ -182,7 +182,7 @@ private: { size_t operator()(const DependencyGroup::Ptr& dependencyGroup) const { - size_t hash = 0; + size_t hash = std::hash{}(dependencyGroup->GetRedundancyGroupName()); for (const auto& [key, group] : dependencyGroup->m_Members) { boost::hash_combine(hash, key); } @@ -194,6 +194,10 @@ private: { bool operator()(const DependencyGroup::Ptr& lhs, const DependencyGroup::Ptr& rhs) const { + if (lhs->GetRedundancyGroupName() != rhs->GetRedundancyGroupName()) { + return false; + } + return std::equal( lhs->m_Members.begin(), lhs->m_Members.end(), rhs->m_Members.begin(), rhs->m_Members.end(), From 7fbb8f74527e2f11c1843064362a4ba1ee46723d Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 14 Feb 2025 13:11:24 +0100 Subject: [PATCH 224/415] Evaluate dependency group state only for a specific child Previously the dependency state was evaluated by picking the first dependency object from the batched members. However, since the dependency `disable_{checks,notifications` attributes aren't taken into account when batching the members, the evaluated state may yield a wrong result for some Checkables due to some random dependency from other Checkable of that group that has the `disable_{checks,notifications` attrs set. This commit forces the callers to always provide the child Checkable the state is evaluated for and picks only the dependency objects of that child Checkable. --- lib/icinga/checkable-dependency.cpp | 2 +- lib/icinga/dependency-group.cpp | 30 ++++++++++++----------------- lib/icinga/dependency.hpp | 2 +- lib/icingadb/icingadb-objects.cpp | 4 ++-- lib/icingadb/icingadb-utility.cpp | 5 +++-- lib/icingadb/icingadb.hpp | 2 +- 6 files changed, 20 insertions(+), 25 deletions(-) diff --git a/lib/icinga/checkable-dependency.cpp b/lib/icinga/checkable-dependency.cpp index b82aebc88..921b36a7e 100644 --- a/lib/icinga/checkable-dependency.cpp +++ b/lib/icinga/checkable-dependency.cpp @@ -206,7 +206,7 @@ bool Checkable::IsReachable(DependencyType dt, int rstack) const } for (auto& dependencyGroup : GetDependencyGroups()) { - if (auto state(dependencyGroup->GetState(dt, rstack + 1)); !state.Reachable || !state.OK) { + if (auto state(dependencyGroup->GetState(this, dt, rstack + 1)); !state.Reachable || !state.OK) { Log(LogDebug, "Checkable") << "Dependency group '" << dependencyGroup->GetRedundancyGroupName() << "' have failed for checkable '" << GetName() << "': Marking as unreachable."; diff --git a/lib/icinga/dependency-group.cpp b/lib/icinga/dependency-group.cpp index 043e8f514..fdf58b5e2 100644 --- a/lib/icinga/dependency-group.cpp +++ b/lib/icinga/dependency-group.cpp @@ -311,29 +311,23 @@ String DependencyGroup::GetCompositeKey() * a dependency group may still be marked as failed even when it has reachable parent Checkables, but an unreachable * group has always a failed state. * + * @param child The child Checkable to evaluate the state for. + * @param dt The dependency type to evaluate the state for, defaults to DependencyState. + * @param rstack The recursion stack level to prevent infinite recursion, defaults to 0. + * * @return - Returns the state of the current dependency group. */ -DependencyGroup::State DependencyGroup::GetState(DependencyType dt, int rstack) const +DependencyGroup::State DependencyGroup::GetState(const Checkable* child, DependencyType dt, int rstack) const { - MembersMap members; - { - // We don't want to hold the mutex lock for the entire evaluation, thus we just need to operate on a copy. - std::lock_guard lock(m_Mutex); - members = m_Members; - } - + auto dependencies(GetDependenciesForChild(child)); size_t reachable = 0, available = 0; - for (auto& [_, dependencies] : members) { - ASSERT(!dependencies.empty()); - - // Dependencies are grouped by parent and all config attributes affecting their availability. Hence, only - // one dependency from the map entry has to be considered, all others will share the same state. - if (auto& [_, dependency] = *dependencies.begin(); dependency->GetParent()->IsReachable(dt, rstack)) { + for (const auto& dependency : dependencies) { + if (dependency->GetParent()->IsReachable(dt, rstack)) { reachable++; - // Only reachable parents are considered for availability. If they are unreachable and checks are disabled, - // they could be incorrectly treated as available otherwise. + // Only reachable parents are considered for availability. If they are unreachable and checks are + // disabled, they could be incorrectly treated as available otherwise. if (dependency->IsAvailable(dt)) { available++; } @@ -346,10 +340,10 @@ DependencyGroup::State DependencyGroup::GetState(DependencyType dt, int rstack) // available as reachable = 0 implies available = 0. return {reachable > 0, available > 0}; } else { - // For dependencies without a redundancy group, members.size() will be 1 in almost all cases. It will only + // For dependencies without a redundancy group, dependencies.size() will be 1 in almost all cases. It will only // contain more elements if there are duplicate dependency config objects between two checkables. In this case, // all of them have to be reachable or available as they don't provide redundancy. Note that unreachable implies // unavailable here as well as only reachable parents count towards the number of available parents. - return {reachable >= members.size(), available == members.size()}; + return {reachable == dependencies.size(), available == dependencies.size()}; } } diff --git a/lib/icinga/dependency.hpp b/lib/icinga/dependency.hpp index 49fb93989..dab2bc4b8 100644 --- a/lib/icinga/dependency.hpp +++ b/lib/icinga/dependency.hpp @@ -170,7 +170,7 @@ public: bool OK; // Whether the dependency group is reachable and OK. }; - State GetState(DependencyType dt = DependencyState, int rstack = 0) const; + State GetState(const Checkable* child, DependencyType dt = DependencyState, int rstack = 0) const; static boost::signals2::signal OnChildRegistered; static boost::signals2::signal&, bool)> OnChildRemoved; diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index 0a5e5eb0f..283770490 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -1210,7 +1210,7 @@ void IcingaDB::InsertCheckableDependencies( // to the DependencyEdgeState HMSETs. The latter is shared by all child Checkables of the current // redundancy group, and since they all depend on the redundancy group, the state of that group is // basically the state of the dependency edges between the children and the redundancy group. - auto stateAttrs(SerializeRedundancyGroupState(dependencyGroup)); + auto stateAttrs(SerializeRedundancyGroupState(checkable, dependencyGroup)); AddDataToHmSets(hMSets, RedisKey::RedundancyGroupState, redundancyGroupId, stateAttrs); AddDataToHmSets(hMSets, RedisKey::DependencyEdgeState, redundancyGroupId, Dictionary::Ptr(new Dictionary{ {"id", redundancyGroupId}, @@ -1416,7 +1416,7 @@ void IcingaDB::UpdateDependenciesState(const Checkable::Ptr& checkable, const De } if (isRedundancyGroup) { - Dictionary::Ptr stateAttrs(SerializeRedundancyGroupState(dependencyGroup)); + Dictionary::Ptr stateAttrs(SerializeRedundancyGroupState(checkable, dependencyGroup)); Dictionary::Ptr sharedGroupState(stateAttrs->ShallowClone()); sharedGroupState->Remove("redundancy_group_id"); diff --git a/lib/icingadb/icingadb-utility.cpp b/lib/icingadb/icingadb-utility.cpp index f53055a50..b93aff67a 100644 --- a/lib/icingadb/icingadb-utility.cpp +++ b/lib/icingadb/icingadb-utility.cpp @@ -201,13 +201,14 @@ Dictionary::Ptr IcingaDB::SerializeDependencyEdgeState(const DependencyGroup::Pt /** * Serialize the provided redundancy group state attributes. * + * @param child The child checkable object to serialize the state for. * @param redundancyGroup The redundancy group object to serialize the state for. * * @return A dictionary with the serialized redundancy group state. */ -Dictionary::Ptr IcingaDB::SerializeRedundancyGroupState(const DependencyGroup::Ptr& redundancyGroup) +Dictionary::Ptr IcingaDB::SerializeRedundancyGroupState(const Checkable::Ptr& child, const DependencyGroup::Ptr& redundancyGroup) { - auto state(redundancyGroup->GetState()); + auto state(redundancyGroup->GetState(child.get())); return new Dictionary{ {"id", redundancyGroup->GetIcingaDBIdentifier()}, {"environment_id", m_EnvironmentId}, diff --git a/lib/icingadb/icingadb.hpp b/lib/icingadb/icingadb.hpp index df454e31a..39a899efc 100644 --- a/lib/icingadb/icingadb.hpp +++ b/lib/icingadb/icingadb.hpp @@ -175,7 +175,7 @@ private: static const char* GetNotificationTypeByEnum(NotificationType type); static Dictionary::Ptr SerializeVars(const Dictionary::Ptr& vars); static Dictionary::Ptr SerializeDependencyEdgeState(const DependencyGroup::Ptr& dependencyGroup, const Dependency::Ptr& dep); - static Dictionary::Ptr SerializeRedundancyGroupState(const DependencyGroup::Ptr& redundancyGroup); + static Dictionary::Ptr SerializeRedundancyGroupState(const Checkable::Ptr& child, const DependencyGroup::Ptr& redundancyGroup); static String HashValue(const Value& value); static String HashValue(const Value& value, const std::set& propertiesBlacklist, bool propertiesWhitelist = false); From a9bb11b16d4b3939cf18534b3bf32c2dff230ab5 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 14 Feb 2025 16:53:47 +0100 Subject: [PATCH 225/415] (Un)register dependencies from parent prior to child Checkable --- lib/icinga/dependency.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/icinga/dependency.cpp b/lib/icinga/dependency.cpp index c45015e8f..52456ff4b 100644 --- a/lib/icinga/dependency.cpp +++ b/lib/icinga/dependency.cpp @@ -251,16 +251,24 @@ void Dependency::OnAllConfigLoaded() // InitChildParentReferences() has to be called before. VERIFY(m_Child && m_Parent); - m_Child->AddDependency(this); + // Icinga DB will implicitly send config updates for the parent Checkable to refresh its affects_children and + // affected_children columns when registering the dependency from the child Checkable. So, we need to register + // the dependency from the parent Checkable first, otherwise the config update of the parent Checkable will change + // nothing at all. m_Parent->AddReverseDependency(this); + m_Child->AddDependency(this); } void Dependency::Stop(bool runtimeRemoved) { ObjectImpl::Stop(runtimeRemoved); - GetChild()->RemoveDependency(this, runtimeRemoved); + // Icinga DB will implicitly send config updates for the parent Checkable to refresh its affects_children and + // affected_children columns when removing the dependency from the child Checkable. So, we need to remove the + // dependency from the parent Checkable first, otherwise the config update of the parent Checkable will change + // nothing at all. GetParent()->RemoveReverseDependency(this); + GetChild()->RemoveDependency(this, runtimeRemoved); } bool Dependency::IsAvailable(DependencyType dt) const From 21cd5e00fa53d52923af6ddc15b24e06fa84cc8a Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 17 Feb 2025 13:49:39 +0100 Subject: [PATCH 226/415] Dependency: Don't allow to update `{period,states,ignore_soft_states}` at runtime --- lib/icinga/dependency.ti | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/icinga/dependency.ti b/lib/icinga/dependency.ti index b58877104..a033420ea 100644 --- a/lib/icinga/dependency.ti +++ b/lib/icinga/dependency.ti @@ -81,16 +81,16 @@ class Dependency : CustomVarObject < DependencyNameComposer [config, no_user_modify] String redundancy_group; - [config, navigation] name(TimePeriod) period (PeriodRaw) { + [config, no_user_modify, navigation] name(TimePeriod) period (PeriodRaw) { navigate {{{ return TimePeriod::GetByName(GetPeriodRaw()); }}} }; - [config] array(Value) states; + [config, no_user_modify] array(Value) states; [no_user_view, no_user_modify] int state_filter_real (StateFilter); - [config] bool ignore_soft_states { + [config, no_user_modify] bool ignore_soft_states { default {{{ return true; }}} }; From da637c3741b0482de241b66908b7a4a8ce57d345 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 17 Feb 2025 17:47:17 +0100 Subject: [PATCH 227/415] IcingaDB: Always send dependencies state HSET updates to Redis --- lib/icingadb/icingadb-objects.cpp | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index 283770490..04043db53 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -1386,6 +1386,7 @@ void IcingaDB::UpdateDependenciesState(const Checkable::Ptr& checkable, const De streamStates.emplace_back(std::move(xAdd)); }); + std::map hMSets; for (auto& dependencyGroup : dependencyGroups) { bool isRedundancyGroup(dependencyGroup->IsRedundancyGroup()); if (isRedundancyGroup && dependencyGroup->GetIcingaDBIdentifier().IsEmpty()) { @@ -1413,6 +1414,7 @@ void IcingaDB::UpdateDependenciesState(const Checkable::Ptr& checkable, const De } addDependencyStateToStream(m_PrefixConfigObject + "dependency:edge:state", stateAttrs); + AddDataToHmSets(hMSets, RedisKey::DependencyEdgeState, stateAttrs->Get("id"), stateAttrs); } if (isRedundancyGroup) { @@ -1425,10 +1427,19 @@ void IcingaDB::UpdateDependenciesState(const Checkable::Ptr& checkable, const De addDependencyStateToStream(m_PrefixConfigObject + "redundancygroup:state", stateAttrs); addDependencyStateToStream(m_PrefixConfigObject + "dependency:edge:state", sharedGroupState); + AddDataToHmSets(hMSets, RedisKey::RedundancyGroupState, dependencyGroup->GetIcingaDBIdentifier(), stateAttrs); + AddDataToHmSets(hMSets, RedisKey::DependencyEdgeState, dependencyGroup->GetIcingaDBIdentifier(), sharedGroupState); } } if (!streamStates.empty()) { + RedisConnection::Queries queries; + for (auto& [redisKey, query] : hMSets) { + query.insert(query.begin(), {"HSET", redisKey}); + queries.emplace_back(std::move(query)); + } + + m_Rcon->FireAndForgetQueries(std::move(queries), Prio::RuntimeStateSync); m_Rcon->FireAndForgetQueries(std::move(streamStates), Prio::RuntimeStateStream, {0, 1}); } } From 945a79e37fd22d2108c378d6e4097b6d38786fc3 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 28 Feb 2025 17:20:03 +0100 Subject: [PATCH 228/415] IcingaDB: Don't send useless dependencies state updates --- lib/icingadb/icingadb-objects.cpp | 19 +++++++++++++++++-- lib/icingadb/icingadb.hpp | 3 ++- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index 04043db53..bd8e74322 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -1357,8 +1357,10 @@ void IcingaDB::UpdateState(const Checkable::Ptr& checkable, StateUpdate mode) * * @param checkable The Checkable you want to send the dependencies state update for * @param onlyDependencyGroup If set, send state updates only for this dependency group and its dependencies. + * @param seenGroups A container to track already processed DependencyGroups to avoid duplicate state updates. */ -void IcingaDB::UpdateDependenciesState(const Checkable::Ptr& checkable, const DependencyGroup::Ptr& onlyDependencyGroup) const +void IcingaDB::UpdateDependenciesState(const Checkable::Ptr& checkable, const DependencyGroup::Ptr& onlyDependencyGroup, + std::set* seenGroups) const { if (!m_Rcon || !m_Rcon->IsConnected()) { return; @@ -1396,6 +1398,18 @@ void IcingaDB::UpdateDependenciesState(const Checkable::Ptr& checkable, const De continue; } + if (seenGroups && !seenGroups->insert(dependencyGroup.get()).second) { + // Usually, if the seenGroups set is provided, IcingaDB is triggering a runtime state update for ALL + // children of a given initiator Checkable (parent). In such cases, we may end up with lots of useless + // state updates as all the children of a non-redundant group a) share the same entry in the database b) + // it doesn't matter which child triggers the state update first all the subsequent updates are just useless. + // + // Likewise, for redundancy groups, all children of a redundancy group share the same set of parents + // and thus the resulting state information would be the same from each child Checkable perspective. + // So, serializing the redundancy group state information only once is sufficient. + continue; + } + auto dependencies(dependencyGroup->GetDependenciesForChild(checkable.get())); std::sort(dependencies.begin(), dependencies.end(), [](const Dependency::Ptr& lhs, const Dependency::Ptr& rhs) { return lhs->GetParent() < rhs->GetParent(); @@ -3096,9 +3110,10 @@ void IcingaDB::StateChangeHandler(const ConfigObject::Ptr& object, const CheckRe void IcingaDB::ReachabilityChangeHandler(const std::set& children) { for (const IcingaDB::Ptr& rw : ConfigType::GetObjectsByType()) { + std::set seenGroups; for (auto& checkable : children) { rw->UpdateState(checkable, StateUpdate::Full); - rw->UpdateDependenciesState(checkable); + rw->UpdateDependenciesState(checkable, nullptr, &seenGroups); } } } diff --git a/lib/icingadb/icingadb.hpp b/lib/icingadb/icingadb.hpp index 39a899efc..af58a977d 100644 --- a/lib/icingadb/icingadb.hpp +++ b/lib/icingadb/icingadb.hpp @@ -114,7 +114,8 @@ private: std::vector* runtimeUpdates, const DependencyGroup::Ptr& onlyDependencyGroup = nullptr); void InsertObjectDependencies(const ConfigObject::Ptr& object, const String typeName, std::map>& hMSets, std::vector& runtimeUpdates, bool runtimeUpdate); - void UpdateDependenciesState(const Checkable::Ptr& checkable, const DependencyGroup::Ptr& onlyDependencyGroup = nullptr) const; + void UpdateDependenciesState(const Checkable::Ptr& checkable, const DependencyGroup::Ptr& onlyDependencyGroup = nullptr, + std::set* seenGroups = nullptr) const; void UpdateState(const Checkable::Ptr& checkable, StateUpdate mode); void SendConfigUpdate(const ConfigObject::Ptr& object, bool runtimeUpdate); void CreateConfigUpdate(const ConfigObject::Ptr& object, const String type, std::map>& hMSets, From 693d094ebc48606675c38c6a18636e597098e817 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Thu, 13 Mar 2025 13:13:06 +0100 Subject: [PATCH 229/415] DependencyGroup: don't change the keys of m_Members after construction This prevents the use of DependencyGroup for storing the dependencies during the early registration (m_DependencyGroupsPushedToRegistry = false), m_PendingDependencies is introduced as a replacement to store the dependencies at that time. --- lib/icinga/checkable-dependency.cpp | 30 +++++++++++----------- lib/icinga/checkable.hpp | 14 ++++++++-- lib/icinga/dependency-group.cpp | 40 ++++++++++++----------------- lib/icinga/dependency.cpp | 2 +- lib/icinga/dependency.hpp | 3 +-- 5 files changed, 46 insertions(+), 43 deletions(-) diff --git a/lib/icinga/checkable-dependency.cpp b/lib/icinga/checkable-dependency.cpp index 921b36a7e..27ea5c0ae 100644 --- a/lib/icinga/checkable-dependency.cpp +++ b/lib/icinga/checkable-dependency.cpp @@ -26,15 +26,12 @@ static constexpr int l_MaxDependencyRecursionLevel(256); void Checkable::PushDependencyGroupsToRegistry() { std::lock_guard lock(m_DependencyMutex); - if (!m_DependencyGroupsPushedToRegistry) { - m_DependencyGroupsPushedToRegistry = true; - - decltype(m_DependencyGroups) dependencyGroups; - m_DependencyGroups.swap(dependencyGroups); - - for (auto& [dependencyGroupKey, dependencyGroup] : dependencyGroups) { - m_DependencyGroups.emplace(dependencyGroupKey, DependencyGroup::Register(dependencyGroup)); + if (m_PendingDependencies != nullptr) { + for (const auto& [key, dependencies] : *m_PendingDependencies) { + String redundancyGroup = std::holds_alternative(key) ? std::get(key) : ""; + m_DependencyGroups.emplace(key, DependencyGroup::Register(new DependencyGroup(redundancyGroup, dependencies))); } + m_PendingDependencies.reset(); } } @@ -78,12 +75,8 @@ void Checkable::AddDependency(const Dependency::Ptr& dependency) std::unique_lock lock(m_DependencyMutex); auto dependencyGroupKey(GetDependencyGroupKey(dependency)); - if (!m_DependencyGroupsPushedToRegistry) { - auto& dependencyGroup = m_DependencyGroups[dependencyGroupKey]; - if (!dependencyGroup) { - dependencyGroup = new DependencyGroup(dependency->GetRedundancyGroup()); - } - dependencyGroup->AddDependency(dependency); + if (m_PendingDependencies != nullptr) { + (*m_PendingDependencies)[dependencyGroupKey].emplace(dependency); return; } @@ -151,10 +144,17 @@ void Checkable::RemoveDependency(const Dependency::Ptr& dependency, bool runtime } } -std::vector Checkable::GetDependencies() const +std::vector Checkable::GetDependencies(bool includePending) const { std::unique_lock lock(m_DependencyMutex); std::vector dependencies; + + if (includePending && m_PendingDependencies != nullptr) { + for (const auto& [group, groupDeps] : *m_PendingDependencies) { + dependencies.insert(dependencies.end(), groupDeps.begin(), groupDeps.end()); + } + } + for (const auto& [_, dependencyGroup] : m_DependencyGroups) { auto tmpDependencies(dependencyGroup->GetDependenciesForChild(this)); dependencies.insert(dependencies.end(), tmpDependencies.begin(), tmpDependencies.end()); diff --git a/lib/icinga/checkable.hpp b/lib/icinga/checkable.hpp index 53db79d72..98c015ed6 100644 --- a/lib/icinga/checkable.hpp +++ b/lib/icinga/checkable.hpp @@ -190,7 +190,7 @@ public: std::vector> GetDependencyGroups() const; void AddDependency(const intrusive_ptr& dependency); void RemoveDependency(const intrusive_ptr& dependency, bool runtimeRemoved = false); - std::vector > GetDependencies() const; + std::vector > GetDependencies(bool includePending = false) const; bool HasAnyDependencies() const; void AddReverseDependency(const intrusive_ptr& dep); @@ -251,9 +251,19 @@ private: /* Dependencies */ mutable std::mutex m_DependencyMutex; - bool m_DependencyGroupsPushedToRegistry{false}; std::map, intrusive_ptr> m_DependencyGroups; std::set > m_ReverseDependencies; + /** + * Registering a checkable to its parent DependencyGroups is delayed during config loading until all dependencies + * were registered on the checkable. m_PendingDependencies is used to temporarily store the dependencies until then. + * It is a pointer type for two reasons: + * 1. The field is no longer needed after the DependencyGroups were registered, having it as a pointer reduces the + * overhead from sizeof(std::map<>) to sizeof(std::map<>*). + * 2. It allows the field to also be used as a flag: the delayed group registration is only done until it is reset + * to nullptr. + */ + std::unique_ptr, std::set>>> + m_PendingDependencies {std::make_unique()}; void GetAllChildrenInternal(std::set& seenChildren, int level = 0) const; diff --git a/lib/icinga/dependency-group.cpp b/lib/icinga/dependency-group.cpp index fdf58b5e2..29d628832 100644 --- a/lib/icinga/dependency-group.cpp +++ b/lib/icinga/dependency-group.cpp @@ -46,17 +46,18 @@ std::pair, bool> DependencyGroup::Unregister(const Dep { std::lock_guard lock(m_RegistryMutex); if (auto it(m_Registry.find(dependencyGroup)); it != m_Registry.end()) { - auto existingGroup(*it); + auto& existingGroup(*it); auto dependencies(existingGroup->GetDependenciesForChild(child.get())); for (const auto& dependency : dependencies) { existingGroup->RemoveDependency(dependency); } - if (existingGroup->IsEmpty()) { + bool remove = !existingGroup->HasChildren(); + if (remove) { m_Registry.erase(it); } - return {{dependencies.begin(), dependencies.end()}, existingGroup->IsEmpty()}; + return {{dependencies.begin(), dependencies.end()}, remove}; } return {{}, false}; } @@ -72,14 +73,11 @@ size_t DependencyGroup::GetRegistrySize() return m_Registry.size(); } -DependencyGroup::DependencyGroup(String name): m_RedundancyGroupName(std::move(name)) -{ -} - -DependencyGroup::DependencyGroup(String name, const std::set& dependencies): m_RedundancyGroupName(std::move(name)) +DependencyGroup::DependencyGroup(String name, const std::set& dependencies) + : m_RedundancyGroupName(std::move(name)) { for (const auto& dependency : dependencies) { - AddDependency(dependency); + m_Members[MakeCompositeKeyFor(dependency)].emplace(dependency->GetChild().get(), dependency.get()); } } @@ -103,14 +101,14 @@ DependencyGroup::CompositeKeyType DependencyGroup::MakeCompositeKeyFor(const Dep } /** - * Check if the current dependency group is empty. + * Check if the current dependency has any children. * - * @return bool - Returns true if the current dependency group has no members, otherwise false. + * @return bool - Returns true if the current dependency group has children, otherwise false. */ -bool DependencyGroup::IsEmpty() const +bool DependencyGroup::HasChildren() const { std::lock_guard lock(m_Mutex); - return m_Members.empty(); + return std::any_of(m_Members.begin(), m_Members.end(), [](const auto& pair) { return !pair.second.empty(); }); } /** @@ -142,7 +140,6 @@ void DependencyGroup::LoadParents(std::set& parents) const { std::lock_guard lock(m_Mutex); for (auto& [compositeKey, children] : m_Members) { - ASSERT(!children.empty()); // We should never have an empty map for any given key at any given time. parents.insert(std::get<0>(compositeKey)); } } @@ -174,11 +171,12 @@ void DependencyGroup::AddDependency(const Dependency::Ptr& dependency) { std::lock_guard lock(m_Mutex); auto compositeKey(MakeCompositeKeyFor(dependency)); - if (auto it(m_Members.find(compositeKey)); it != m_Members.end()) { - it->second.emplace(dependency->GetChild().get(), dependency.get()); - } else { - m_Members.emplace(compositeKey, MemberValueType{{dependency->GetChild().get(), dependency.get()}}); - } + auto it = m_Members.find(compositeKey); + + // The dependency must be compatible with the group, i.e. its parent config must be known in the group already. + VERIFY(it != m_Members.end()); + + it->second.emplace(dependency->GetChild().get(), dependency.get()); } /** @@ -196,10 +194,6 @@ void DependencyGroup::RemoveDependency(const Dependency::Ptr& dependency) // This will also remove the child Checkable from the multimap container // entirely if this was the last child of it. it->second.erase(childrenIt); - // If the composite key has no more children left, we can remove it entirely as well. - if (it->second.empty()) { - m_Members.erase(it); - } return; } } diff --git a/lib/icinga/dependency.cpp b/lib/icinga/dependency.cpp index 52456ff4b..cf4af8627 100644 --- a/lib/icinga/dependency.cpp +++ b/lib/icinga/dependency.cpp @@ -123,7 +123,7 @@ public: } // Explicitly configured dependency objects - for (const auto& dep : checkable->GetDependencies()) { + for (const auto& dep : checkable->GetDependencies(/* includePending = */ true)) { m_Stack.emplace_back(dep); AssertNoCycle(dep->GetParent()); m_Stack.pop_back(); diff --git a/lib/icinga/dependency.hpp b/lib/icinga/dependency.hpp index dab2bc4b8..26a7dffe0 100644 --- a/lib/icinga/dependency.hpp +++ b/lib/icinga/dependency.hpp @@ -132,7 +132,6 @@ public: using MemberValueType = std::unordered_multimap; using MembersMap = std::map; - explicit DependencyGroup(String name); DependencyGroup(String name, const std::set& dependencies); static DependencyGroup::Ptr Register(const DependencyGroup::Ptr& dependencyGroup); @@ -151,7 +150,7 @@ public: return !m_RedundancyGroupName.IsEmpty(); } - bool IsEmpty() const; + bool HasChildren() const; void AddDependency(const Dependency::Ptr& dependency); void RemoveDependency(const Dependency::Ptr& dependency); std::vector GetDependenciesForChild(const Checkable* child) const; From 864e2aaae0009e34171b10822b50b4ba1f986942 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 13 Mar 2025 16:37:26 +0100 Subject: [PATCH 230/415] Drop superfluous mutex lock & don't manually unpack `std::tuple` --- lib/icinga/dependency-group.cpp | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/lib/icinga/dependency-group.cpp b/lib/icinga/dependency-group.cpp index 29d628832..0ffe33741 100644 --- a/lib/icinga/dependency-group.cpp +++ b/lib/icinga/dependency-group.cpp @@ -138,7 +138,6 @@ std::vector DependencyGroup::GetDependenciesForChild(const Chec */ void DependencyGroup::LoadParents(std::set& parents) const { - std::lock_guard lock(m_Mutex); for (auto& [compositeKey, children] : m_Members) { parents.insert(std::get<0>(compositeKey)); } @@ -203,7 +202,7 @@ void DependencyGroup::RemoveDependency(const Dependency::Ptr& dependency) /** * Copy the dependency objects of the current dependency group to the provided dependency group (destination). * - * @param dest The dependency group to move the dependencies to. + * @param dest The dependency group to copy the dependencies to. */ void DependencyGroup::CopyDependenciesTo(const DependencyGroup::Ptr& dest) { @@ -274,12 +273,9 @@ String DependencyGroup::GetCompositeKey() // not achievable using pointers. using StringTuple = std::tuple; std::vector compositeKeys; - { - std::lock_guard lock(m_Mutex); - for (auto& [compositeKey, _] : m_Members) { - auto [parent, tp, stateFilter, ignoreSoftStates] = compositeKey; - compositeKeys.emplace_back(parent->GetName(), tp ? tp->GetName() : "", stateFilter, ignoreSoftStates); - } + for (auto& [compositeKey, _] : m_Members) { + auto [parent, tp, stateFilter, ignoreSoftStates] = compositeKey; + compositeKeys.emplace_back(parent->GetName(), tp ? tp->GetName() : "", stateFilter, ignoreSoftStates); } // IMPORTANT: The order of the composite keys must be sorted to ensure the deterministic hash value. @@ -287,11 +283,10 @@ String DependencyGroup::GetCompositeKey() Array::Ptr data(new Array{GetRedundancyGroupName()}); for (auto& compositeKey : compositeKeys) { - auto [parent, tp, stateFilter, ignoreSoftStates] = compositeKey; - data->Add(std::move(parent)); - data->Add(std::move(tp)); - data->Add(stateFilter); - data->Add(ignoreSoftStates); + // std::apply is used to unpack the composite key tuple and add its elements to the data array. + // It's like manually expanding the tuple into x variables and then adding them one by one to the array. + // See https://en.cppreference.com/w/cpp/language/fold for more information. + std::apply([&data](auto&&... args) { (data->Add(std::move(args)), ...); }, std::move(compositeKey)); } return PackObject(data); From 065118bc22d420d22f89c60fb1d85955e9a64394 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Thu, 13 Mar 2025 15:02:56 +0100 Subject: [PATCH 231/415] Make DependencyGroup::State an enum The previous struct used two bools to represent three useful states. Make this more explicit by having these three states as an enum. --- lib/icinga/checkable-dependency.cpp | 2 +- lib/icinga/dependency-group.cpp | 22 ++++++++++++++++------ lib/icinga/dependency.hpp | 7 +------ lib/icingadb/icingadb-utility.cpp | 4 ++-- 4 files changed, 20 insertions(+), 15 deletions(-) diff --git a/lib/icinga/checkable-dependency.cpp b/lib/icinga/checkable-dependency.cpp index 27ea5c0ae..65d9dd902 100644 --- a/lib/icinga/checkable-dependency.cpp +++ b/lib/icinga/checkable-dependency.cpp @@ -206,7 +206,7 @@ bool Checkable::IsReachable(DependencyType dt, int rstack) const } for (auto& dependencyGroup : GetDependencyGroups()) { - if (auto state(dependencyGroup->GetState(this, dt, rstack + 1)); !state.Reachable || !state.OK) { + if (auto state(dependencyGroup->GetState(this, dt, rstack + 1)); state != DependencyGroup::State::Ok) { Log(LogDebug, "Checkable") << "Dependency group '" << dependencyGroup->GetRedundancyGroupName() << "' have failed for checkable '" << GetName() << "': Marking as unreachable."; diff --git a/lib/icinga/dependency-group.cpp b/lib/icinga/dependency-group.cpp index 0ffe33741..aa7d5fc06 100644 --- a/lib/icinga/dependency-group.cpp +++ b/lib/icinga/dependency-group.cpp @@ -325,14 +325,24 @@ DependencyGroup::State DependencyGroup::GetState(const Checkable* child, Depende if (IsRedundancyGroup()) { // The state of a redundancy group is determined by the best state of any parent. If any parent ist reachable, - // the redundancy group is reachable, analogously for availability. Note that an unreachable group cannot be - // available as reachable = 0 implies available = 0. - return {reachable > 0, available > 0}; + // the redundancy group is reachable, analogously for availability. + if (reachable == 0) { + return State::Unreachable; + } else if (available == 0) { + return State::Failed; + } else { + return State::Ok; + } } else { // For dependencies without a redundancy group, dependencies.size() will be 1 in almost all cases. It will only // contain more elements if there are duplicate dependency config objects between two checkables. In this case, - // all of them have to be reachable or available as they don't provide redundancy. Note that unreachable implies - // unavailable here as well as only reachable parents count towards the number of available parents. - return {reachable == dependencies.size(), available == dependencies.size()}; + // all of them have to be reachable/available as they don't provide redundancy. + if (reachable < dependencies.size()) { + return State::Unreachable; + } else if (available < dependencies.size()) { + return State::Failed; + } else { + return State::Ok; + } } } diff --git a/lib/icinga/dependency.hpp b/lib/icinga/dependency.hpp index 26a7dffe0..b4e206b7f 100644 --- a/lib/icinga/dependency.hpp +++ b/lib/icinga/dependency.hpp @@ -163,12 +163,7 @@ public: const String& GetRedundancyGroupName() const; String GetCompositeKey(); - struct State - { - bool Reachable; // Whether the dependency group is reachable. - bool OK; // Whether the dependency group is reachable and OK. - }; - + enum class State { Ok, Failed, Unreachable }; State GetState(const Checkable* child, DependencyType dt = DependencyState, int rstack = 0) const; static boost::signals2::signal OnChildRegistered; diff --git a/lib/icingadb/icingadb-utility.cpp b/lib/icingadb/icingadb-utility.cpp index b93aff67a..89e5a5031 100644 --- a/lib/icingadb/icingadb-utility.cpp +++ b/lib/icingadb/icingadb-utility.cpp @@ -213,8 +213,8 @@ Dictionary::Ptr IcingaDB::SerializeRedundancyGroupState(const Checkable::Ptr& ch {"id", redundancyGroup->GetIcingaDBIdentifier()}, {"environment_id", m_EnvironmentId}, {"redundancy_group_id", redundancyGroup->GetIcingaDBIdentifier()}, - {"failed", !state.Reachable || !state.OK}, - {"is_reachable", state.Reachable}, + {"failed", state != DependencyGroup::State::Ok}, + {"is_reachable", state != DependencyGroup::State::Unreachable}, {"last_state_change", TimestampToMilliseconds(Utility::GetTime())}, }; } From a943c4588b1165ac273f0a9df87c5f7e23865281 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 25 Mar 2025 13:04:41 +0100 Subject: [PATCH 232/415] Zone#GetEndpoints(): return endpoints in the specified order, not randomly ApiListener#RelayMessageOne() relays every given message to the first connected endpoint Zone#GetEndpoints() returns. Randomness in combination with bad luck can direct more traffic (from a particular network segment) to one master than the admin wants. This change lets the Zone#endpoints order prefer one endpoint over the other. --- lib/icinga/apiactions.cpp | 2 +- lib/icinga/clusterevents.cpp | 2 +- lib/livestatus/zonestable.cpp | 3 +-- lib/remote/zone.cpp | 7 +++---- lib/remote/zone.hpp | 2 +- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/icinga/apiactions.cpp b/lib/icinga/apiactions.cpp index 6e9fddd4d..c84b39918 100644 --- a/lib/icinga/apiactions.cpp +++ b/lib/icinga/apiactions.cpp @@ -932,7 +932,7 @@ Dictionary::Ptr ApiActions::ExecuteCommand(const ConfigObject::Ptr& object, cons for (const Zone::Ptr& zone : ConfigType::GetObjectsByType()) { /* Fetch immediate child zone members */ if (zone->GetParent() == localZone && zone->CanAccessObject(endpointPtr->GetZone())) { - std::set endpoints = zone->GetEndpoints(); + auto endpoints (zone->GetEndpoints()); for (const Endpoint::Ptr& childEndpoint : endpoints) { if (!(childEndpoint->GetCapabilities() & (uint_fast64_t)ApiCapabilities::ExecuteArbitraryCommand)) { diff --git a/lib/icinga/clusterevents.cpp b/lib/icinga/clusterevents.cpp index b49d2071d..ea29d1ece 100644 --- a/lib/icinga/clusterevents.cpp +++ b/lib/icinga/clusterevents.cpp @@ -966,7 +966,7 @@ Value ClusterEvents::ExecuteCommandAPIHandler(const MessageOrigin::Ptr& origin, for (const Zone::Ptr &zone : ConfigType::GetObjectsByType()) { /* Fetch immediate child zone members */ if (zone->GetParent() == localZone && zone->CanAccessObject(endpointZone)) { - std::set endpoints = zone->GetEndpoints(); + auto endpoints (zone->GetEndpoints()); for (const Endpoint::Ptr &childEndpoint : endpoints) { if (!(childEndpoint->GetCapabilities() & (uint_fast64_t)ApiCapabilities::ExecuteArbitraryCommand)) { diff --git a/lib/livestatus/zonestable.cpp b/lib/livestatus/zonestable.cpp index d86cc7256..b5498a62a 100644 --- a/lib/livestatus/zonestable.cpp +++ b/lib/livestatus/zonestable.cpp @@ -70,8 +70,7 @@ Value ZonesTable::EndpointsAccessor(const Value& row) if (!zone) return Empty; - std::set endpoints = zone->GetEndpoints(); - + auto endpoints (zone->GetEndpoints()); ArrayData result; for (const Endpoint::Ptr& endpoint : endpoints) { diff --git a/lib/remote/zone.cpp b/lib/remote/zone.cpp index 5ae1468c1..9af662a24 100644 --- a/lib/remote/zone.cpp +++ b/lib/remote/zone.cpp @@ -51,10 +51,9 @@ Zone::Ptr Zone::GetParent() const return m_Parent; } -std::set Zone::GetEndpoints() const +std::vector Zone::GetEndpoints() const { - std::set result; - + std::vector result; Array::Ptr endpoints = GetEndpointsRaw(); if (endpoints) { @@ -66,7 +65,7 @@ std::set Zone::GetEndpoints() const if (!endpoint) continue; - result.insert(endpoint); + result.emplace_back(std::move(endpoint)); } } diff --git a/lib/remote/zone.hpp b/lib/remote/zone.hpp index 897b18e96..af3a9db23 100644 --- a/lib/remote/zone.hpp +++ b/lib/remote/zone.hpp @@ -22,7 +22,7 @@ public: void OnAllConfigLoaded() override; Zone::Ptr GetParent() const; - std::set GetEndpoints() const; + std::vector GetEndpoints() const; std::vector GetAllParentsRaw() const; Array::Ptr GetAllParents() const override; From bc2c750551faed595f4983a02738022da1d9168a Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 26 Mar 2025 10:48:37 +0100 Subject: [PATCH 233/415] IcingaDB: Don't stream runtime state updates to Redis --- lib/icingadb/icingadb-objects.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index bd8e74322..40580a358 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -3383,10 +3383,12 @@ void IcingaDB::DeleteState(const String& id, RedisKey redisKey, bool hasChecksum hdels.emplace_back(RedisConnection::Query{"HDEL", m_PrefixConfigObject + redisKeyWithoutPrefix, id}); m_Rcon->FireAndForgetQueries(std::move(hdels), Prio::RuntimeStateSync); - m_Rcon->FireAndForgetQueries({{ + // TODO: This is currently purposefully commented out due to how Icinga DB (Go) handles runtime state + // upsert and delete events. See https://github.com/Icinga/icingadb/pull/894 for more details. + /*m_Rcon->FireAndForgetQueries({{ "XADD", "icinga:runtime:state", "MAXLEN", "~", "1000000", "*", "redis_key", m_PrefixConfigObject + redisKeyWithoutPrefix, "id", id, "runtime_type", "delete" - }}, Prio::RuntimeStateStream, {0, 1}); + }}, Prio::RuntimeStateStream, {0, 1});*/ } /** From a1865e1b43a6793ae555c59e3aee69fb80f913d7 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Mon, 31 Mar 2025 12:58:20 +0200 Subject: [PATCH 234/415] Service::GetSeverity(): simplify nested if, add braces No change in functionality, just making the code a bit nicer and more compact. --- lib/icinga/service.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/icinga/service.cpp b/lib/icinga/service.cpp index d831136bb..2993916d3 100644 --- a/lib/icinga/service.cpp +++ b/lib/icinga/service.cpp @@ -139,13 +139,12 @@ int Service::GetSeverity() const ObjectLock hlock (host); if (host->GetState() != HostUp) { severity += 1024; + } else if (IsAcknowledged()) { + severity += 512; + } else if (IsInDowntime()) { + severity += 256; } else { - if (IsAcknowledged()) - severity += 512; - else if (IsInDowntime()) - severity += 256; - else - severity += 2048; + severity += 2048; } hlock.Unlock(); } From 5ca6047b35f280a9d625fe15c65fdf657a0fdd7a Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Mon, 31 Mar 2025 13:01:03 +0200 Subject: [PATCH 235/415] Service::GetSeverity(): replace switch with if No change in functionality, just making the code a bit more compact. --- lib/icinga/service.cpp | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/icinga/service.cpp b/lib/icinga/service.cpp index 2993916d3..954cd3d5e 100644 --- a/lib/icinga/service.cpp +++ b/lib/icinga/service.cpp @@ -121,18 +121,14 @@ int Service::GetSeverity() const } else if (state == ServiceOK) { severity = 0; } else { - switch (state) { - case ServiceWarning: - severity = 32; - break; - case ServiceUnknown: - severity = 64; - break; - case ServiceCritical: - severity = 128; - break; - default: - severity = 256; + if (state == ServiceWarning) { + severity = 32; + } else if (state == ServiceUnknown) { + severity = 64; + } else if (state == ServiceCritical) { + severity = 128; + } else { + severity = 256; } Host::Ptr host = GetHost(); From 01acfb47a99928dc378e60336a606cdaceb40276 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Mon, 31 Mar 2025 13:03:43 +0200 Subject: [PATCH 236/415] Service::GetHost(): return early to remove a nesting level No change in functionality. The first two branches actually set the final return value for the method, so they can just return directly, removing the need to have the rest of the function inside an else block. --- lib/icinga/service.cpp | 57 +++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/lib/icinga/service.cpp b/lib/icinga/service.cpp index 954cd3d5e..406c7958c 100644 --- a/lib/icinga/service.cpp +++ b/lib/icinga/service.cpp @@ -111,39 +111,40 @@ Host::Ptr Service::GetHost() const * sort by severity. It is therefore easier to keep them seperated here. */ int Service::GetSeverity() const { - int severity; - ObjectLock olock(this); ServiceState state = GetStateRaw(); if (!HasBeenChecked()) { - severity = 16; - } else if (state == ServiceOK) { - severity = 0; - } else { - if (state == ServiceWarning) { - severity = 32; - } else if (state == ServiceUnknown) { - severity = 64; - } else if (state == ServiceCritical) { - severity = 128; - } else { - severity = 256; - } - - Host::Ptr host = GetHost(); - ObjectLock hlock (host); - if (host->GetState() != HostUp) { - severity += 1024; - } else if (IsAcknowledged()) { - severity += 512; - } else if (IsInDowntime()) { - severity += 256; - } else { - severity += 2048; - } - hlock.Unlock(); + return 16; } + if (state == ServiceOK) { + return 0; + } + + int severity = 0; + + if (state == ServiceWarning) { + severity = 32; + } else if (state == ServiceUnknown) { + severity = 64; + } else if (state == ServiceCritical) { + severity = 128; + } else { + severity = 256; + } + + Host::Ptr host = GetHost(); + ObjectLock hlock (host); + if (host->GetState() != HostUp) { + severity += 1024; + } else if (IsAcknowledged()) { + severity += 512; + } else if (IsInDowntime()) { + severity += 256; + } else { + severity += 2048; + } + hlock.Unlock(); olock.Unlock(); From c899d52e2fd5487ab07c87e5ce79693045c13360 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Mon, 31 Mar 2025 13:05:47 +0200 Subject: [PATCH 237/415] Service::GetSeverity(): remove explicit unlocking No change in functionality. The ObjectLock destructor will implicitly release the locks when returning from the function. --- lib/icinga/service.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/icinga/service.cpp b/lib/icinga/service.cpp index 406c7958c..11d7c9633 100644 --- a/lib/icinga/service.cpp +++ b/lib/icinga/service.cpp @@ -144,9 +144,6 @@ int Service::GetSeverity() const } else { severity += 2048; } - hlock.Unlock(); - - olock.Unlock(); return severity; } From 6443f8997fa7bc20900548698b38e38d3beae9c7 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Mon, 31 Mar 2025 13:19:21 +0200 Subject: [PATCH 238/415] Host::GetSeverity(): add braces to if statements No change in functionality, just makes the code a bit nicer. --- lib/icinga/host.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/icinga/host.cpp b/lib/icinga/host.cpp index 36149d3dc..0fe7ec8eb 100644 --- a/lib/icinga/host.cpp +++ b/lib/icinga/host.cpp @@ -180,17 +180,19 @@ int Host::GetSeverity() const } else if (state == HostUp) { severity = 0; } else { - if (IsReachable()) + if (IsReachable()) { severity = 64; - else + } else { severity = 32; + } - if (IsAcknowledged()) + if (IsAcknowledged()) { severity += 512; - else if (IsInDowntime()) + } else if (IsInDowntime()) { severity += 256; - else + } else { severity += 2048; + } } olock.Unlock(); From 2ebee010f03e7e27eb45add59e65b4c1ca64ff1c Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Mon, 31 Mar 2025 13:21:02 +0200 Subject: [PATCH 239/415] Host::GetHost(): return early to remove a nesting level No change in functionality. The first two branches actually set the final return value for the method, so they can just return directly, removing the need to have the rest of the function inside an else block. --- lib/icinga/host.cpp | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/lib/icinga/host.cpp b/lib/icinga/host.cpp index 0fe7ec8eb..0dbe361a9 100644 --- a/lib/icinga/host.cpp +++ b/lib/icinga/host.cpp @@ -170,29 +170,30 @@ HostState Host::GetLastHardState() const * sort by severity. It is therefore easier to keep them seperated here. */ int Host::GetSeverity() const { - int severity = 0; - ObjectLock olock(this); HostState state = GetState(); if (!HasBeenChecked()) { - severity = 16; - } else if (state == HostUp) { - severity = 0; - } else { - if (IsReachable()) { - severity = 64; - } else { - severity = 32; - } + return 16; + } + if (state == HostUp) { + return 0; + } - if (IsAcknowledged()) { - severity += 512; - } else if (IsInDowntime()) { - severity += 256; - } else { - severity += 2048; - } + int severity = 0; + + if (IsReachable()) { + severity = 64; + } else { + severity = 32; + } + + if (IsAcknowledged()) { + severity += 512; + } else if (IsInDowntime()) { + severity += 256; + } else { + severity += 2048; } olock.Unlock(); From d8271c656872ee98c1a3d888054315b94459c798 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Mon, 31 Mar 2025 13:23:07 +0200 Subject: [PATCH 240/415] Host::GetSeverity(): remove explicit unlocking No change in functionality. The ObjectLock destructor will implicitly release the locks when returning from the function. --- lib/icinga/host.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/icinga/host.cpp b/lib/icinga/host.cpp index 0dbe361a9..b85f8ccc9 100644 --- a/lib/icinga/host.cpp +++ b/lib/icinga/host.cpp @@ -196,8 +196,6 @@ int Host::GetSeverity() const severity += 2048; } - olock.Unlock(); - return severity; } From 1e05a166f13a718e443e20fad33dc968e8727270 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Mon, 31 Mar 2025 13:25:01 +0200 Subject: [PATCH 241/415] Host::GetSeverity(): remove empty line at end of method --- lib/icinga/host.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/icinga/host.cpp b/lib/icinga/host.cpp index b85f8ccc9..e50ba9b6f 100644 --- a/lib/icinga/host.cpp +++ b/lib/icinga/host.cpp @@ -197,7 +197,6 @@ int Host::GetSeverity() const } return severity; - } bool Host::IsStateOK(ServiceState state) const From 31a224c5095bc7918b2ca84875c8e54415ab2550 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Mon, 31 Mar 2025 13:53:14 +0200 Subject: [PATCH 242/415] Checkable::GetSeverity(): always take reachability into account So far, Service::GetSeverity() only considered the state of its own host, i.e. the implicit service to its own host dependency, and treated it similar to acknowledgements and downtimes. In contrast, Host::GetSeverity() considered reachability and treated it like a state, i.e. for the severity calculation, the host was either up, down, or unreachable. This commit changes the following things: 1. Make the service severity also consider explicitly configured dependencies by using IsReachable(). 2. Prefer acknowledgements and downtimes over unreachability in the severity calculation so that if an already acknowledged or in-downtime services (i.e. already handled service) becomes unreachable, it shouln't become more severe. 3. To unify host and service severities a bit, hosts now use the same logic that treats reachability more like acknowledgements/downtimes instead of like a state (changing the other way around would the state from the check plugin would not affect the severity for unrachable services anymore). --- lib/icinga/host.cpp | 10 +++------- lib/icinga/service.cpp | 8 +++----- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/lib/icinga/host.cpp b/lib/icinga/host.cpp index e50ba9b6f..22dd79b40 100644 --- a/lib/icinga/host.cpp +++ b/lib/icinga/host.cpp @@ -180,18 +180,14 @@ int Host::GetSeverity() const return 0; } - int severity = 0; - - if (IsReachable()) { - severity = 64; - } else { - severity = 32; - } + int severity = 32; // DOWN if (IsAcknowledged()) { severity += 512; } else if (IsInDowntime()) { severity += 256; + } else if (!IsReachable()) { + severity += 1024; } else { severity += 2048; } diff --git a/lib/icinga/service.cpp b/lib/icinga/service.cpp index 11d7c9633..c24647c82 100644 --- a/lib/icinga/service.cpp +++ b/lib/icinga/service.cpp @@ -133,14 +133,12 @@ int Service::GetSeverity() const severity = 256; } - Host::Ptr host = GetHost(); - ObjectLock hlock (host); - if (host->GetState() != HostUp) { - severity += 1024; - } else if (IsAcknowledged()) { + if (IsAcknowledged()) { severity += 512; } else if (IsInDowntime()) { severity += 256; + } else if (!IsReachable()) { + severity += 1024; } else { severity += 2048; } From 33838a620a0712f666da9499cd77367a5116fa38 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Thu, 3 Apr 2025 09:33:42 +0200 Subject: [PATCH 243/415] GHA: Fix Alpine After CMAKE_OPTS Refactoring The just merged Alpine CI run for LibreSSL from #9949 failed since it missed the changes of the refactoring PR #10369. This change applied the refactoring for Alpine as well, hopefully making the CI happy. --- .github/workflows/linux.bash | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/linux.bash b/.github/workflows/linux.bash index ec53adabe..6c4240d1a 100755 --- a/.github/workflows/linux.bash +++ b/.github/workflows/linux.bash @@ -15,8 +15,6 @@ case "$DISTRO" in apk add bison boost-dev ccache cmake flex g++ libedit-dev libressl-dev ninja-build tzdata ln -vs /usr/lib/ninja-build/bin/ninja /usr/local/bin/ninja - CMAKE_OPTS="-DUSE_SYSTEMD=OFF -DICINGA2_WITH_MYSQL=OFF -DICINGA2_WITH_PGSQL=OFF" - # This test fails due to some glibc/musl mismatch regarding timezone PST/PDT. # - https://www.openwall.com/lists/musl/2024/03/05/2 # - https://gitlab.alpinelinux.org/alpine/aports/-/blob/b3ea02e2251451f9511086e1970f21eb640097f7/community/icinga2/disable-failing-tests.patch @@ -85,6 +83,9 @@ case "$DISTRO" in esac case "$DISTRO" in + alpine:*) + CMAKE_OPTS+=(-DUSE_SYSTEMD=OFF -DICINGA2_WITH_MYSQL=OFF -DICINGA2_WITH_PGSQL=OFF) + ;; debian:*|ubuntu:*) CMAKE_OPTS+=(-DICINGA2_LTO_BUILD=ON) source <(dpkg-buildflags --export=sh) From 7f164bda96341272be385fa1359a26f97eb9d2b4 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 3 Apr 2025 10:01:43 +0200 Subject: [PATCH 244/415] Raise cmake minimum required version to `3.8...3.17` CMake version `< 3.5` is no longer supported, so the new CMake minimum policy version is set to `3.8` to support C++17 unconditionally. After checking all the policies that might affect Icinga 2 in any way, CMake `3.17` is used as a max supported CMake policy. Anything above that may work but we didn't explicitly verify the policies introduced with CMake 3.18 and later and may or may not affect Icinga 2. --- CMakeLists.txt | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6041840d6..b65845972 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,17 +1,12 @@ # Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ -cmake_minimum_required(VERSION 2.8.12) +# CMake 3.8 is required, CMake policy compatibility was verified up to 3.17. +cmake_minimum_required(VERSION 3.8...3.17) set(BOOST_MIN_VERSION "1.66.0") -if("${CMAKE_VERSION}" VERSION_LESS "3.8") # SLES 12.5 - if(NOT MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++17") - endif() -else() - set(CMAKE_CXX_STANDARD 17) - set(CMAKE_CXX_STANDARD_REQUIRED ON) - set(CMAKE_CXX_EXTENSIONS OFF) -endif() +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) project(icinga2) list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") From 28c61c904a74db5181a8cc1f3a81fad544d7eab7 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 3 Apr 2025 16:43:31 +0200 Subject: [PATCH 245/415] Fix CMake doesn't export symbols of executables anymore CMake 3.4 introduced a new policy [^1] which prevents from automatically adding the compiler flags needed for exporting the symbols of the executables and libraries without the `ENABLE_EXPORTS` property. So, by defining this variable, CMake will restore the previous behaviour by automatically adding the `ENABLE_EXPORTS` properties to all targets. [1]: https://cmake.org/cmake/help/latest/policy/CMP0065.html --- CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index b65845972..023131554 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,6 +18,10 @@ if(NOT CMAKE_BUILD_TYPE) FORCE) endif() +# Include symbols in executables so that function names can be printed in stack traces, for example in crash dumps. +set(CMAKE_ENABLE_EXPORTS ON) # Added in CMake 3.4 +set(CMAKE_EXECUTABLE_ENABLE_EXPORTS ON) # Added in CMake 3.27 and supersedes the above one. + if(WIN32) set(ICINGA2_MASTER OFF) else() From cff8c60ba95918c810e5d513cacbc74458acd647 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 3 Apr 2025 15:56:22 +0200 Subject: [PATCH 246/415] Drop superfluous `MACOSX_RPATH` definition It's on by default since CMake version `3.0` --- CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 023131554..fce41f411 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -234,7 +234,6 @@ if(WIN32) list(APPEND base_DEPS ws2_32 dbghelp shlwapi msi) endif() -set(CMAKE_MACOSX_RPATH 1) set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH};${CMAKE_INSTALL_FULL_LIBDIR}/icinga2") if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") From 5a30554db9d1a41e92c094933d6dd4ad843feaf4 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 3 Apr 2025 16:24:24 +0200 Subject: [PATCH 247/415] Drop superfluous CMake modules These modules are already provided by CMake itself, so we don't need to ship them by ourselves. --- third-party/cmake/FindBISON.cmake | 221 ---------------------- third-party/cmake/FindFLEX.cmake | 185 ------------------- third-party/cmake/FindGit.cmake | 73 -------- third-party/cmake/GNUInstallDirs.cmake | 245 ------------------------- 4 files changed, 724 deletions(-) delete mode 100644 third-party/cmake/FindBISON.cmake delete mode 100644 third-party/cmake/FindFLEX.cmake delete mode 100644 third-party/cmake/FindGit.cmake delete mode 100644 third-party/cmake/GNUInstallDirs.cmake diff --git a/third-party/cmake/FindBISON.cmake b/third-party/cmake/FindBISON.cmake deleted file mode 100644 index 6c6b420c2..000000000 --- a/third-party/cmake/FindBISON.cmake +++ /dev/null @@ -1,221 +0,0 @@ -# - Find bison executable and provides macros to generate custom build rules -# The module defines the following variables: -# -# BISON_EXECUTABLE - path to the bison program -# BISON_VERSION - version of bison -# BISON_FOUND - true if the program was found -# -# If bison is found, the module defines the macros: -# BISON_TARGET( [VERBOSE ] -# [COMPILE_FLAGS ] [HEADER ]) -# which will create a custom rule to generate a parser. is -# the path to a yacc file. is the name of the source file -# generated by bison. A header file containing the token list is also -# generated according to bison's -d option by default or if the HEADER -# option is used, the argument is passed to bison's --defines option to -# specify output file. If COMPILE_FLAGS option is specified, the next -# parameter is added in the bison command line. if VERBOSE option is -# specified, is created and contains verbose descriptions of the -# grammar and parser. The macro defines a set of variables: -# BISON_${Name}_DEFINED - true is the macro ran successfully -# BISON_${Name}_INPUT - The input source file, an alias for -# BISON_${Name}_OUTPUT_SOURCE - The source file generated by bison -# BISON_${Name}_OUTPUT_HEADER - The header file generated by bison -# BISON_${Name}_OUTPUTS - The sources files generated by bison -# BISON_${Name}_COMPILE_FLAGS - Options used in the bison command line -# -# ==================================================================== -# Example: -# -# find_package(BISON) -# BISON_TARGET(MyParser parser.y ${CMAKE_CURRENT_BINARY_DIR}/parser.cpp) -# add_executable(Foo main.cpp ${BISON_MyParser_OUTPUTS}) -# ==================================================================== - -#============================================================================= -# Copyright 2009 Kitware, Inc. -# Copyright 2006 Tristan Carel -# Modified 2010 by Jon Siwek, adding HEADER option -# -# Distributed under the OSI-approved BSD License (the "License"): -# CMake - Cross Platform Makefile Generator -# Copyright 2000-2009 Kitware, Inc., Insight Software Consortium -# All rights reserved. - -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the names of Kitware, Inc., the Insight Software Consortium, -# nor the names of their contributors may be used to endorse or promote -# products derived from this software without specific prior written -# permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= - -FIND_PROGRAM(BISON_EXECUTABLE bison DOC "path to the bison executable") -MARK_AS_ADVANCED(BISON_EXECUTABLE) - -IF(BISON_EXECUTABLE) - - EXECUTE_PROCESS(COMMAND ${BISON_EXECUTABLE} --version - OUTPUT_VARIABLE BISON_version_output - ERROR_VARIABLE BISON_version_error - RESULT_VARIABLE BISON_version_result - OUTPUT_STRIP_TRAILING_WHITESPACE) - IF(NOT ${BISON_version_result} EQUAL 0) - MESSAGE(SEND_ERROR "Command \"${BISON_EXECUTABLE} --version\" failed with output:\n${BISON_version_error}") - ELSE() - STRING(REGEX REPLACE "^bison \\(GNU Bison\\) ([^\n]+)\n.*" "\\1" - BISON_VERSION "${BISON_version_output}") - ENDIF() - - # internal macro - MACRO(BISON_TARGET_option_verbose Name BisonOutput filename) - LIST(APPEND BISON_TARGET_cmdopt "--verbose") - GET_FILENAME_COMPONENT(BISON_TARGET_output_path "${BisonOutput}" PATH) - GET_FILENAME_COMPONENT(BISON_TARGET_output_name "${BisonOutput}" NAME_WE) - ADD_CUSTOM_COMMAND(OUTPUT ${filename} - COMMAND ${CMAKE_COMMAND} - ARGS -E copy - "${BISON_TARGET_output_path}/${BISON_TARGET_output_name}.output" - "${filename}" - DEPENDS - "${BISON_TARGET_output_path}/${BISON_TARGET_output_name}.output" - COMMENT "[BISON][${Name}] Copying bison verbose table to ${filename}" - WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) - SET(BISON_${Name}_VERBOSE_FILE ${filename}) - LIST(APPEND BISON_TARGET_extraoutputs - "${BISON_TARGET_output_path}/${BISON_TARGET_output_name}.output") - ENDMACRO(BISON_TARGET_option_verbose) - - # internal macro - MACRO(BISON_TARGET_option_extraopts Options) - SET(BISON_TARGET_extraopts "${Options}") - SEPARATE_ARGUMENTS(BISON_TARGET_extraopts) - LIST(APPEND BISON_TARGET_cmdopt ${BISON_TARGET_extraopts}) - ENDMACRO(BISON_TARGET_option_extraopts) - - #============================================================ - # BISON_TARGET (public macro) - #============================================================ - # - MACRO(BISON_TARGET Name BisonInput BisonOutput) - SET(BISON_TARGET_output_header "") - #SET(BISON_TARGET_command_opt "") - SET(BISON_TARGET_cmdopt "") - SET(BISON_TARGET_outputs "${BisonOutput}") - IF(NOT ${ARGC} EQUAL 3 AND - NOT ${ARGC} EQUAL 5 AND - NOT ${ARGC} EQUAL 7 AND - NOT ${ARGC} EQUAL 9) - MESSAGE(SEND_ERROR "Usage") - ELSE() - # Parsing parameters - IF(${ARGC} GREATER 5 OR ${ARGC} EQUAL 5) - IF("${ARGV3}" STREQUAL "VERBOSE") - BISON_TARGET_option_verbose(${Name} ${BisonOutput} "${ARGV4}") - ENDIF() - IF("${ARGV3}" STREQUAL "COMPILE_FLAGS") - BISON_TARGET_option_extraopts("${ARGV4}") - ENDIF() - IF("${ARGV3}" STREQUAL "HEADER") - set(BISON_TARGET_output_header "${ARGV4}") - ENDIF() - ENDIF() - - IF(${ARGC} GREATER 7 OR ${ARGC} EQUAL 7) - IF("${ARGV5}" STREQUAL "VERBOSE") - BISON_TARGET_option_verbose(${Name} ${BisonOutput} "${ARGV6}") - ENDIF() - - IF("${ARGV5}" STREQUAL "COMPILE_FLAGS") - BISON_TARGET_option_extraopts("${ARGV6}") - ENDIF() - - IF("${ARGV5}" STREQUAL "HEADER") - set(BISON_TARGET_output_header "${ARGV6}") - ENDIF() - ENDIF() - - IF(${ARGC} EQUAL 9) - IF("${ARGV7}" STREQUAL "VERBOSE") - BISON_TARGET_option_verbose(${Name} ${BisonOutput} "${ARGV8}") - ENDIF() - - IF("${ARGV7}" STREQUAL "COMPILE_FLAGS") - BISON_TARGET_option_extraopts("${ARGV8}") - ENDIF() - - IF("${ARGV7}" STREQUAL "HEADER") - set(BISON_TARGET_output_header "${ARGV8}") - ENDIF() - ENDIF() - - IF(BISON_TARGET_output_header) - # Header's name passed in as argument to be used in --defines option - LIST(APPEND BISON_TARGET_cmdopt - "--defines=${BISON_TARGET_output_header}") - set(BISON_${Name}_OUTPUT_HEADER ${BISON_TARGET_output_header}) - ELSE() - # Header's name generated by bison (see option -d) - LIST(APPEND BISON_TARGET_cmdopt "-d") - STRING(REGEX REPLACE "^(.*)(\\.[^.]*)$" "\\2" _fileext "${ARGV2}") - STRING(REPLACE "c" "h" _fileext ${_fileext}) - STRING(REGEX REPLACE "^(.*)(\\.[^.]*)$" "\\1${_fileext}" - BISON_${Name}_OUTPUT_HEADER "${ARGV2}") - ENDIF() - - LIST(APPEND BISON_TARGET_outputs "${BISON_${Name}_OUTPUT_HEADER}") - - ADD_CUSTOM_COMMAND(OUTPUT ${BISON_TARGET_outputs} - ${BISON_TARGET_extraoutputs} - COMMAND ${BISON_EXECUTABLE} - ARGS ${BISON_TARGET_cmdopt} -o ${ARGV2} ${ARGV1} - DEPENDS ${ARGV1} - COMMENT "[BISON][${Name}] Building parser with bison ${BISON_VERSION}" - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) - - # define target variables - SET(BISON_${Name}_DEFINED TRUE) - SET(BISON_${Name}_INPUT ${ARGV1}) - SET(BISON_${Name}_OUTPUTS ${BISON_TARGET_outputs}) - SET(BISON_${Name}_COMPILE_FLAGS ${BISON_TARGET_cmdopt}) - SET(BISON_${Name}_OUTPUT_SOURCE "${BisonOutput}") - - ENDIF(NOT ${ARGC} EQUAL 3 AND - NOT ${ARGC} EQUAL 5 AND - NOT ${ARGC} EQUAL 7 AND - NOT ${ARGC} EQUAL 9) - ENDMACRO(BISON_TARGET) - # - #============================================================ - -ENDIF(BISON_EXECUTABLE) - -INCLUDE(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(BISON DEFAULT_MSG BISON_EXECUTABLE) - -# FindBISON.cmake ends here \ No newline at end of file diff --git a/third-party/cmake/FindFLEX.cmake b/third-party/cmake/FindFLEX.cmake deleted file mode 100644 index 7cd5c84f5..000000000 --- a/third-party/cmake/FindFLEX.cmake +++ /dev/null @@ -1,185 +0,0 @@ -# - Find flex executable and provides a macro to generate custom build rules -# -# The module defines the following variables: -# FLEX_FOUND - true is flex executable is found -# FLEX_EXECUTABLE - the path to the flex executable -# FLEX_VERSION - the version of flex -# FLEX_LIBRARIES - The flex libraries -# -# The minimum required version of flex can be specified using the -# standard syntax, e.g. FIND_PACKAGE(FLEX 2.5.13) -# -# -# If flex is found on the system, the module provides the macro: -# FLEX_TARGET(Name FlexInput FlexOutput [COMPILE_FLAGS ]) -# which creates a custom command to generate the file from -# the file. If COMPILE_FLAGS option is specified, the next -# parameter is added to the flex command line. Name is an alias used to -# get details of this custom command. Indeed the macro defines the -# following variables: -# FLEX_${Name}_DEFINED - true is the macro ran successfully -# FLEX_${Name}_OUTPUTS - the source file generated by the custom rule, an -# alias for FlexOutput -# FLEX_${Name}_INPUT - the flex source file, an alias for ${FlexInput} -# -# Flex scanners oftenly use tokens defined by Bison: the code generated -# by Flex depends of the header generated by Bison. This module also -# defines a macro: -# ADD_FLEX_BISON_DEPENDENCY(FlexTarget BisonTarget) -# which adds the required dependency between a scanner and a parser -# where and are the first parameters of -# respectively FLEX_TARGET and BISON_TARGET macros. -# -# ==================================================================== -# Example: -# -# find_package(BISON) -# find_package(FLEX) -# -# BISON_TARGET(MyParser parser.y ${CMAKE_CURRENT_BINARY_DIR}/parser.cpp -# FLEX_TARGET(MyScanner lexer.l ${CMAKE_CURRENT_BIANRY_DIR}/lexer.cpp) -# ADD_FLEX_BISON_DEPENDENCY(MyScanner MyParser) -# -# include_directories(${CMAKE_CURRENT_BINARY_DIR}) -# add_executable(Foo -# Foo.cc -# ${BISON_MyParser_OUTPUTS} -# ${FLEX_MyScanner_OUTPUTS} -# ) -# ==================================================================== - -#============================================================================= -# Copyright 2009 Kitware, Inc. -# Copyright 2006 Tristan Carel -# Modified 2010 by Jon Siwek, backporting for CMake 2.6 compat -# -# Distributed under the OSI-approved BSD License (the "License"): -# CMake - Cross Platform Makefile Generator -# Copyright 2000-2009 Kitware, Inc., Insight Software Consortium -# All rights reserved. - -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# * Neither the names of Kitware, Inc., the Insight Software Consortium, -# nor the names of their contributors may be used to endorse or promote -# products derived from this software without specific prior written -# permission. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= - -FIND_PROGRAM(FLEX_EXECUTABLE flex DOC "path to the flex executable") -MARK_AS_ADVANCED(FLEX_EXECUTABLE) - -FIND_LIBRARY(FL_LIBRARY NAMES fl - DOC "path to the fl library") -MARK_AS_ADVANCED(FL_LIBRARY) -SET(FLEX_LIBRARIES ${FL_LIBRARY}) - -IF(FLEX_EXECUTABLE) - GET_FILENAME_COMPONENT(FLEX_EXECUTABLE_NAME ${FLEX_EXECUTABLE} NAME) - EXECUTE_PROCESS(COMMAND ${FLEX_EXECUTABLE} --version - OUTPUT_VARIABLE FLEX_version_output - ERROR_VARIABLE FLEX_version_error - RESULT_VARIABLE FLEX_version_result - OUTPUT_STRIP_TRAILING_WHITESPACE) - IF(NOT ${FLEX_version_result} EQUAL 0) - IF(FLEX_FIND_REQUIRED) - MESSAGE(SEND_ERROR "Command \"${FLEX_EXECUTABLE} --version\" failed with output:\n${FLEX_version_output}\n${FLEX_version_error}") - ELSE() - MESSAGE("Command \"${FLEX_EXECUTABLE} --version\" failed with output:\n${FLEX_version_output}\n${FLEX_version_error}\nFLEX_VERSION will not be available") - ENDIF() - ELSE() - STRING(REGEX REPLACE "^${FLEX_EXECUTABLE_NAME}[^ ]* (.*)$" "\\1" - FLEX_VERSION "${FLEX_version_output}") - ENDIF() - - IF(FLEX_FIND_VERSION) - IF("${FLEX_VERSION}" VERSION_LESS "${FLEX_FIND_VERSION}") - MESSAGE(SEND_ERROR "Your version of flex is too old. You can specify an alternative path using -DFLEX_EXECUTABLE=/path/to/flex") - ENDIF() - ENDIF() - - #============================================================ - # FLEX_TARGET (public macro) - #============================================================ - # - MACRO(FLEX_TARGET Name Input Output) - SET(FLEX_TARGET_usage "FLEX_TARGET( [COMPILE_FLAGS ]") - IF(${ARGC} GREATER 3) - IF(${ARGC} EQUAL 5) - IF("${ARGV3}" STREQUAL "COMPILE_FLAGS") - SET(FLEX_EXECUTABLE_opts "${ARGV4}") - SEPARATE_ARGUMENTS(FLEX_EXECUTABLE_opts) - ELSE() - MESSAGE(SEND_ERROR ${FLEX_TARGET_usage}) - ENDIF() - ELSE() - MESSAGE(SEND_ERROR ${FLEX_TARGET_usage}) - ENDIF() - ENDIF() - - ADD_CUSTOM_COMMAND(OUTPUT ${Output} - COMMAND ${FLEX_EXECUTABLE} - ARGS ${FLEX_EXECUTABLE_opts} -o${Output} ${Input} - DEPENDS ${Input} - COMMENT "[FLEX][${Name}] Building scanner with flex ${FLEX_VERSION}" - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) - - SET(FLEX_${Name}_DEFINED TRUE) - SET(FLEX_${Name}_OUTPUTS ${Output}) - SET(FLEX_${Name}_INPUT ${Input}) - SET(FLEX_${Name}_COMPILE_FLAGS ${FLEX_EXECUTABLE_opts}) - ENDMACRO(FLEX_TARGET) - #============================================================ - - - #============================================================ - # ADD_FLEX_BISON_DEPENDENCY (public macro) - #============================================================ - # - MACRO(ADD_FLEX_BISON_DEPENDENCY FlexTarget BisonTarget) - - IF(NOT FLEX_${FlexTarget}_OUTPUTS) - MESSAGE(SEND_ERROR "Flex target `${FlexTarget}' does not exists.") - ENDIF() - - IF(NOT BISON_${BisonTarget}_OUTPUT_HEADER) - MESSAGE(SEND_ERROR "Bison target `${BisonTarget}' does not exists.") - ENDIF() - - SET_SOURCE_FILES_PROPERTIES(${FLEX_${FlexTarget}_OUTPUTS} - PROPERTIES OBJECT_DEPENDS ${BISON_${BisonTarget}_OUTPUT_HEADER}) - ENDMACRO(ADD_FLEX_BISON_DEPENDENCY) - #============================================================ - -ENDIF(FLEX_EXECUTABLE) - -INCLUDE(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(FLEX FLEX_EXECUTABLE - FLEX_VERSION) - -# FindFLEX.cmake ends here diff --git a/third-party/cmake/FindGit.cmake b/third-party/cmake/FindGit.cmake deleted file mode 100644 index d23e6f1ca..000000000 --- a/third-party/cmake/FindGit.cmake +++ /dev/null @@ -1,73 +0,0 @@ -#.rst: -# FindGit -# ------- -# -# -# -# The module defines the following variables: -# -# :: -# -# GIT_EXECUTABLE - path to git command line client -# GIT_FOUND - true if the command line client was found -# GIT_VERSION_STRING - the version of git found (since CMake 2.8.8) -# -# Example usage: -# -# :: -# -# find_package(Git) -# if(GIT_FOUND) -# message("git found: ${GIT_EXECUTABLE}") -# endif() - -#============================================================================= -# Copyright 2010 Kitware, Inc. -# Copyright 2012 Rolf Eike Beer -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distribute this file outside of CMake, substitute the full -# License text for the above reference.) - -# Look for 'git' or 'eg' (easy git) -# -set(git_names git eg) - -# Prefer .cmd variants on Windows unless running in a Makefile -# in the MSYS shell. -# -if(WIN32) - if(NOT CMAKE_GENERATOR MATCHES "MSYS") - set(git_names git.cmd git eg.cmd eg) - endif() -endif() - -find_program(GIT_EXECUTABLE - NAMES ${git_names} - PATH_SUFFIXES Git/cmd Git/bin - DOC "git command line client" - ) -mark_as_advanced(GIT_EXECUTABLE) - -if(GIT_EXECUTABLE) - execute_process(COMMAND ${GIT_EXECUTABLE} --version - OUTPUT_VARIABLE git_version - ERROR_QUIET - OUTPUT_STRIP_TRAILING_WHITESPACE) - if (git_version MATCHES "^git version [0-9]") - string(REPLACE "git version " "" GIT_VERSION_STRING "${git_version}") - endif() -endif() - -# Handle the QUIETLY and REQUIRED arguments and set GIT_FOUND to TRUE if -# all listed variables are TRUE - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(Git GIT_EXECUTABLE - GIT_VERSION_STRING) diff --git a/third-party/cmake/GNUInstallDirs.cmake b/third-party/cmake/GNUInstallDirs.cmake deleted file mode 100644 index 60e90994a..000000000 --- a/third-party/cmake/GNUInstallDirs.cmake +++ /dev/null @@ -1,245 +0,0 @@ -#.rst: -# GNUInstallDirs -# -------------- -# -# Define GNU standard installation directories -# -# Provides install directory variables as defined for GNU software: -# -# :: -# -# http://www.gnu.org/prep/standards/html_node/Directory-Variables.html -# -# Inclusion of this module defines the following variables: -# -# :: -# -# CMAKE_INSTALL_ - destination for files of a given type -# CMAKE_INSTALL_FULL_ - corresponding absolute path -# -# where is one of: -# -# :: -# -# BINDIR - user executables (bin) -# SBINDIR - system admin executables (sbin) -# LIBEXECDIR - program executables (libexec) -# SYSCONFDIR - read-only single-machine data (etc) -# SHAREDSTATEDIR - modifiable architecture-independent data (com) -# LOCALSTATEDIR - modifiable single-machine data (var) -# LIBDIR - object code libraries (lib or lib64 or lib/ on Debian) -# INCLUDEDIR - C header files (include) -# OLDINCLUDEDIR - C header files for non-gcc (/usr/include) -# DATAROOTDIR - read-only architecture-independent data root (share) -# DATADIR - read-only architecture-independent data (DATAROOTDIR) -# INFODIR - info documentation (DATAROOTDIR/info) -# LOCALEDIR - locale-dependent data (DATAROOTDIR/locale) -# MANDIR - man documentation (DATAROOTDIR/man) -# DOCDIR - documentation root (DATAROOTDIR/doc/PROJECT_NAME) -# -# Each CMAKE_INSTALL_ value may be passed to the DESTINATION -# options of install() commands for the corresponding file type. If the -# includer does not define a value the above-shown default will be used -# and the value will appear in the cache for editing by the user. Each -# CMAKE_INSTALL_FULL_ value contains an absolute path constructed -# from the corresponding destination by prepending (if necessary) the -# value of CMAKE_INSTALL_PREFIX. - -#============================================================================= -# Copyright 2011 Nikita Krupen'ko -# Copyright 2011 Kitware, Inc. -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distribute this file outside of CMake, substitute the full -# License text for the above reference.) - -# Installation directories -# -if(NOT DEFINED CMAKE_INSTALL_BINDIR) - set(CMAKE_INSTALL_BINDIR "bin" CACHE PATH "user executables (bin)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_SBINDIR) - set(CMAKE_INSTALL_SBINDIR "sbin" CACHE PATH "system admin executables (sbin)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_LIBEXECDIR) - set(CMAKE_INSTALL_LIBEXECDIR "libexec" CACHE PATH "program executables (libexec)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_SYSCONFDIR) - set(CMAKE_INSTALL_SYSCONFDIR "etc" CACHE PATH "read-only single-machine data (etc)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_SHAREDSTATEDIR) - set(CMAKE_INSTALL_SHAREDSTATEDIR "com" CACHE PATH "modifiable architecture-independent data (com)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_LOCALSTATEDIR) - set(CMAKE_INSTALL_LOCALSTATEDIR "var" CACHE PATH "modifiable single-machine data (var)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_LIBDIR) - set(_LIBDIR_DEFAULT "lib") - # Override this default 'lib' with 'lib64' iff: - # - we are on Linux system but NOT cross-compiling - # - we are NOT on debian - # - we are on a 64 bits system - # reason is: amd64 ABI: http://www.x86-64.org/documentation/abi.pdf - # For Debian with multiarch, use 'lib/${CMAKE_LIBRARY_ARCHITECTURE}' if - # CMAKE_LIBRARY_ARCHITECTURE is set (which contains e.g. "i386-linux-gnu" - # and CMAKE_INSTALL_PREFIX is "/usr" - # See http://wiki.debian.org/Multiarch - if(DEFINED _GNUInstallDirs_LAST_CMAKE_INSTALL_PREFIX) - set(__LAST_LIBDIR_DEFAULT "lib") - # __LAST_LIBDIR_DEFAULT is the default value that we compute from - # _GNUInstallDirs_LAST_CMAKE_INSTALL_PREFIX, not a cache entry for - # the value that was last used as the default. - # This value is used to figure out whether the user changed the - # CMAKE_INSTALL_LIBDIR value manually, or if the value was the - # default one. When CMAKE_INSTALL_PREFIX changes, the value is - # updated to the new default, unless the user explicitly changed it. - endif() - if(CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU)$" - AND NOT CMAKE_CROSSCOMPILING) - if (EXISTS "/etc/debian_version") # is this a debian system ? - if(CMAKE_LIBRARY_ARCHITECTURE) - if("${CMAKE_INSTALL_PREFIX}" MATCHES "^/usr/?$") - set(_LIBDIR_DEFAULT "lib/${CMAKE_LIBRARY_ARCHITECTURE}") - endif() - if(DEFINED _GNUInstallDirs_LAST_CMAKE_INSTALL_PREFIX - AND "${_GNUInstallDirs_LAST_CMAKE_INSTALL_PREFIX}" MATCHES "^/usr/?$") - set(__LAST_LIBDIR_DEFAULT "lib/${CMAKE_LIBRARY_ARCHITECTURE}") - endif() - endif() - else() # not debian, rely on CMAKE_SIZEOF_VOID_P: - if(NOT DEFINED CMAKE_SIZEOF_VOID_P) - message(AUTHOR_WARNING - "Unable to determine default CMAKE_INSTALL_LIBDIR directory because no target architecture is known. " - "Please enable at least one language before including GNUInstallDirs.") - else() - if("${CMAKE_SIZEOF_VOID_P}" EQUAL "8") - set(_LIBDIR_DEFAULT "lib64") - if(DEFINED _GNUInstallDirs_LAST_CMAKE_INSTALL_PREFIX) - set(__LAST_LIBDIR_DEFAULT "lib64") - endif() - endif() - endif() - endif() - endif() - if(NOT DEFINED CMAKE_INSTALL_LIBDIR) - set(CMAKE_INSTALL_LIBDIR "${_LIBDIR_DEFAULT}" CACHE PATH "object code libraries (${_LIBDIR_DEFAULT})") - elseif(DEFINED __LAST_LIBDIR_DEFAULT - AND "${__LAST_LIBDIR_DEFAULT}" STREQUAL "${CMAKE_INSTALL_LIBDIR}") - set_property(CACHE CMAKE_INSTALL_LIBDIR PROPERTY VALUE "${_LIBDIR_DEFAULT}") - endif() -endif() -# Save for next run -set(_GNUInstallDirs_LAST_CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}" CACHE INTERNAL "CMAKE_INSTALL_PREFIX during last run") - - -if(NOT DEFINED CMAKE_INSTALL_INCLUDEDIR) - set(CMAKE_INSTALL_INCLUDEDIR "include" CACHE PATH "C header files (include)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_OLDINCLUDEDIR) - set(CMAKE_INSTALL_OLDINCLUDEDIR "/usr/include" CACHE PATH "C header files for non-gcc (/usr/include)") -endif() - -if(NOT DEFINED CMAKE_INSTALL_DATAROOTDIR) - set(CMAKE_INSTALL_DATAROOTDIR "share" CACHE PATH "read-only architecture-independent data root (share)") -endif() - -#----------------------------------------------------------------------------- -# Values whose defaults are relative to DATAROOTDIR. Store empty values in -# the cache and store the defaults in local variables if the cache values are -# not set explicitly. This auto-updates the defaults as DATAROOTDIR changes. - -if(NOT CMAKE_INSTALL_DATADIR) - set(CMAKE_INSTALL_DATADIR "" CACHE PATH "read-only architecture-independent data (DATAROOTDIR)") - set(CMAKE_INSTALL_DATADIR "${CMAKE_INSTALL_DATAROOTDIR}") -endif() - -if(CMAKE_SYSTEM_NAME MATCHES "(DragonFly|FreeBSD|OpenBSD|NetBSD)") - if(NOT CMAKE_INSTALL_INFODIR) - set(CMAKE_INSTALL_INFODIR "" CACHE PATH "info documentation (info)") - set(CMAKE_INSTALL_INFODIR "info") - endif() - - if(NOT CMAKE_INSTALL_MANDDIR) - set(CMAKE_INSTALL_MANDIR "" CACHE PATH "man documentation (man)") - set(CMAKE_INSTALL_MANDIR "man") - endif() -else() - if(NOT CMAKE_INSTALL_INFODIR) - set(CMAKE_INSTALL_INFODIR "" CACHE PATH "info documentation (DATAROOTDIR/info)") - set(CMAKE_INSTALL_INFODIR "${CMAKE_INSTALL_DATAROOTDIR}/info") - endif() - - if(NOT CMAKE_INSTALL_MANDDIR) - set(CMAKE_INSTALL_MANDIR "" CACHE PATH "man documentation (DATAROOTDIR/man)") - set(CMAKE_INSTALL_MANDIR "${CMAKE_INSTALL_DATAROOTDIR}/man") - endif() -endif() - -if(NOT CMAKE_INSTALL_LOCALEDIR) - set(CMAKE_INSTALL_LOCALEDIR "" CACHE PATH "locale-dependent data (DATAROOTDIR/locale)") - set(CMAKE_INSTALL_LOCALEDIR "${CMAKE_INSTALL_DATAROOTDIR}/locale") -endif() - -if(NOT CMAKE_INSTALL_DOCDIR) - set(CMAKE_INSTALL_DOCDIR "" CACHE PATH "documentation root (DATAROOTDIR/doc/PROJECT_NAME)") - set(CMAKE_INSTALL_DOCDIR "${CMAKE_INSTALL_DATAROOTDIR}/doc/${PROJECT_NAME}") -endif() - -#----------------------------------------------------------------------------- - -mark_as_advanced( - CMAKE_INSTALL_BINDIR - CMAKE_INSTALL_SBINDIR - CMAKE_INSTALL_LIBEXECDIR - CMAKE_INSTALL_SYSCONFDIR - CMAKE_INSTALL_SHAREDSTATEDIR - CMAKE_INSTALL_LOCALSTATEDIR - CMAKE_INSTALL_LIBDIR - CMAKE_INSTALL_INCLUDEDIR - CMAKE_INSTALL_OLDINCLUDEDIR - CMAKE_INSTALL_DATAROOTDIR - CMAKE_INSTALL_DATADIR - CMAKE_INSTALL_INFODIR - CMAKE_INSTALL_LOCALEDIR - CMAKE_INSTALL_MANDIR - CMAKE_INSTALL_DOCDIR - ) - -# Result directories -# -foreach(dir - BINDIR - SBINDIR - LIBEXECDIR - SYSCONFDIR - SHAREDSTATEDIR - LOCALSTATEDIR - LIBDIR - INCLUDEDIR - OLDINCLUDEDIR - DATAROOTDIR - DATADIR - INFODIR - LOCALEDIR - MANDIR - DOCDIR - ) - if(NOT IS_ABSOLUTE ${CMAKE_INSTALL_${dir}}) - set(CMAKE_INSTALL_FULL_${dir} "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_${dir}}") - else() - set(CMAKE_INSTALL_FULL_${dir} "${CMAKE_INSTALL_${dir}}") - endif() -endforeach() From 9ecf7714e3cbd219766f0bb2d01799a311c1430e Mon Sep 17 00:00:00 2001 From: Silas <67681686+Tqnsls@users.noreply.github.com> Date: Tue, 8 Apr 2025 09:02:02 +0200 Subject: [PATCH 248/415] Update command-plugins.conf to ensure compatibility with nagios-plugins' check_disk (#10395) * Update command-plugins.conf to ensure compatibility with nagios-plugins' check_disk * Update 10-icinga-template-library.md * Update 10-icinga-template-library.md --- doc/10-icinga-template-library.md | 1 + itl/command-plugins.conf | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md index b719f9916..da4a5d992 100644 --- a/doc/10-icinga-template-library.md +++ b/doc/10-icinga-template-library.md @@ -434,6 +434,7 @@ disk\_units | **Optional.** Choose bytes, kB, MB, GB, TB. disk\_exclude\_type | **Optional.** Ignore all filesystems of indicated type. Multiple regular expression strings must be defined as array. Defaults to "none", "tmpfs", "sysfs", "proc", "configfs", "devtmpfs", "devfs", "mtmfs", "tracefs", "cgroup", "fuse.\*" (only Monitoring Plugins support this so far), "fuse.gvfsd-fuse", "fuse.gvfs-fuse-daemon", "fuse.sshfs", "fdescfs", "overlay", "nsfs", "squashfs". disk\_include\_type | **Optional.** Check only filesystems of indicated type. Multiple regular expression strings must be defined as array. disk\_inode\_perfdata | **Optional.** Display inode usage in perfdata +disk\_np\_inode\_perfdata | **Optional.** Enable performance data for inode-based statistics (Requires: nagios-plugins >= 2.3.0) disk\_extra\_opts | **Optional.** Read extra plugin options from an ini file. ### disk_smb diff --git a/itl/command-plugins.conf b/itl/command-plugins.conf index e3eb5e370..06246bbe6 100644 --- a/itl/command-plugins.conf +++ b/itl/command-plugins.conf @@ -1659,6 +1659,10 @@ object CheckCommand "disk" { description = "Display inode usage in perfdata" set_if = "$disk_inode_perfdata$" } + "--inode-perfdata" = { + description = "Enable performance data for inode-based statistics (nagios-plugins)" + set_if = "$disk_np_inode_perfdata$" + } "-p" = { value = "$disk_partitions$" description = "Path or partition (may be repeated)" From 8ab859d828850009f0e492086eb62782f2509d82 Mon Sep 17 00:00:00 2001 From: Silas <67681686+Tqnsls@users.noreply.github.com> Date: Tue, 8 Apr 2025 09:18:38 +0200 Subject: [PATCH 249/415] `itl/ssl_cert`: Add `--ignore-maximum-validity` option (#10396) * Update web.conf - Include "--ignore-maximum-validity" * Update 10-icinga-template-library.md * Update 10-icinga-template-library.md * Update 10-icinga-template-library.md --- doc/10-icinga-template-library.md | 73 ++++++++++++++++--------------- itl/plugins-contrib.d/web.conf | 4 ++ 2 files changed, 41 insertions(+), 36 deletions(-) diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md index da4a5d992..773a9924e 100644 --- a/doc/10-icinga-template-library.md +++ b/doc/10-icinga-template-library.md @@ -5957,42 +5957,43 @@ Custom variables passed as [command parameters](03-monitoring-basics.md#command- Name | Description --------------------------|-------------- -ssl_cert_address | **Optional.** The host's address. Defaults to "$address$" if the host's `address` attribute is set, "$address6$" otherwise. -ssl_cert_port | **Optional.** TCP port number (default: 443). -ssl_cert_proxy | **Optional.** Proxy server to use for connecting to the host. Sets http_proxy and the s_client -proxy option. -ssl_cert_file | **Optional.** Local file path. Works only if `ssl_cert_address` is set to "localhost". -ssl_cert_warn | **Optional.** Minimum number of days a certificate has to be valid. -ssl_cert_critical | **Optional.** Minimum number of days a certificate has to be valid to issue a critical status. -ssl_cert_maximum_validity | **Optional.** Maximum number of days a certificate is allowed to be valid (default: 397) -ssl_cert_cn | **Optional.** Pattern to match the CN or AltName of the certificate. -ssl_cert_issuer | **Optional.** Pattern to match the issuer of the certificate. -ssl_cert_org | **Optional.** Pattern to match the organization of the certificate. -ssl_cert_email | **Optional.** Pattern to match the email address contained in the certificate. -ssl_cert_serial | **Optional.** Pattern to match the serial number. -ssl_cert_noauth | **Optional.** Ignore authority warnings (expiration only) -ssl_cert_match_host | **Optional.** Match CN with the host name. -ssl_cert_selfsigned | **Optional.** Allow self-signed certificate. -ssl_cert_sni | **Optional.** Sets the TLS SNI (Server Name Indication) extension. -ssl_cert_timeout | **Optional.** Seconds before connection times out (default: 15) -ssl_cert_protocol | **Optional.** Use the specific protocol {http,smtp,pop3,imap,ftp,xmpp,irc,ldap} (default: http). -ssl_cert_http_url | **Optional.** HTTP Request URL (default: /) -ssl_cert_clientcert | **Optional.** Use client certificate to authenticate. -ssl_cert_clientpass | **Optional.** Set passphrase for client certificate. -ssl_cert_ssllabs | **Optional.** SSL Labs assessment -ssl_cert_ssllabs_nocache | **Optional.** Forces a new check by SSL Labs -ssl_cert_rootcert | **Optional.** Root certificate or directory to be used for certificate validation. -ssl_cert_ignore_signature | **Optional.** Do not check if the certificate was signed with SHA1 od MD5. -ssl_cert_ssl_version | **Optional.** Force specific SSL version out of {ssl2,ssl3,tls1,tls1_1,tls1_2}. -ssl_cert_disable_ssl_versions | **Optional.** Disable specific SSL versions out of {ssl2,ssl3,tls1,tls1_1,tls1_2}. Multiple versions can be given as array. -ssl_cert_cipher | **Optional.** Cipher selection: force {ecdsa,rsa} authentication. -ssl_cert_ignore_expiration | **Optional.** Ignore expiration date. -ssl_cert_ignore_host_cn | **Optional.** Do not complain if the CN does not match. -ssl_cert_ignore_ocsp | **Optional.** Do not check revocation with OCSP. -ssl_cert_ignore_ocsp_errors | **Optional.** Continue if the OCSP status cannot be checked. -ssl_cert_ignore_ocsp_timeout | **Optional.** Ignore OCSP result when timeout occurs while checking. -ssl_cert_ignore_sct | **Optional.** Do not check for signed certificate timestamps. -ssl_cert_ignore_tls_renegotiation | **Optional.** Do not check for renegotiation. -ssl_cert_dane | **Optional.** Verify that valid DANE records exist ({211,301,302,311,312} or empty string). +ssl_cert_address | **Optional.** The host's address. Defaults to "$address$" if the host's `address` attribute is set, "$address6$" otherwise. +ssl_cert_port | **Optional.** TCP port number (default: 443). +ssl_cert_proxy | **Optional.** Proxy server to use for connecting to the host. Sets http_proxy and the s_client -proxy option. +ssl_cert_file | **Optional.** Local file path. Works only if `ssl_cert_address` is set to "localhost". +ssl_cert_warn | **Optional.** Minimum number of days a certificate has to be valid. +ssl_cert_critical | **Optional.** Minimum number of days a certificate has to be valid to issue a critical status. +ssl_cert_maximum_validity | **Optional.** Maximum number of days a certificate is allowed to be valid (default: 397) +ssl_cert_ignore_maximum_validity | **Optional.** Ignore the certificate maximum validity +ssl_cert_cn | **Optional.** Pattern to match the CN or AltName of the certificate. +ssl_cert_issuer | **Optional.** Pattern to match the issuer of the certificate. +ssl_cert_org | **Optional.** Pattern to match the organization of the certificate. +ssl_cert_email | **Optional.** Pattern to match the email address contained in the certificate. +ssl_cert_serial | **Optional.** Pattern to match the serial number. +ssl_cert_noauth | **Optional.** Ignore authority warnings (expiration only) +ssl_cert_match_host | **Optional.** Match CN with the host name. +ssl_cert_selfsigned | **Optional.** Allow self-signed certificate. +ssl_cert_sni | **Optional.** Sets the TLS SNI (Server Name Indication) extension. +ssl_cert_timeout | **Optional.** Seconds before connection times out (default: 15) +ssl_cert_protocol | **Optional.** Use the specific protocol {http,smtp,pop3,imap,ftp,xmpp,irc,ldap} (default: http). +ssl_cert_http_url | **Optional.** HTTP Request URL (default: /) +ssl_cert_clientcert | **Optional.** Use client certificate to authenticate. +ssl_cert_clientpass | **Optional.** Set passphrase for client certificate. +ssl_cert_ssllabs | **Optional.** SSL Labs assessment +ssl_cert_ssllabs_nocache | **Optional.** Forces a new check by SSL Labs +ssl_cert_rootcert | **Optional.** Root certificate or directory to be used for certificate validation. +ssl_cert_ignore_signature | **Optional.** Do not check if the certificate was signed with SHA1 od MD5. +ssl_cert_ssl_version | **Optional.** Force specific SSL version out of {ssl2,ssl3,tls1,tls1_1,tls1_2}. +ssl_cert_disable_ssl_versions | **Optional.** Disable specific SSL versions out of {ssl2,ssl3,tls1,tls1_1,tls1_2}. Multiple versions can be given as array. +ssl_cert_cipher | **Optional.** Cipher selection: force {ecdsa,rsa} authentication. +ssl_cert_ignore_expiration | **Optional.** Ignore expiration date. +ssl_cert_ignore_host_cn | **Optional.** Do not complain if the CN does not match. +ssl_cert_ignore_ocsp | **Optional.** Do not check revocation with OCSP. +ssl_cert_ignore_ocsp_errors | **Optional.** Continue if the OCSP status cannot be checked. +ssl_cert_ignore_ocsp_timeout | **Optional.** Ignore OCSP result when timeout occurs while checking. +ssl_cert_ignore_sct | **Optional.** Do not check for signed certificate timestamps. +ssl_cert_ignore_tls_renegotiation | **Optional.** Do not check for renegotiation. +ssl_cert_dane | **Optional.** Verify that valid DANE records exist ({211,301,302,311,312} or empty string). #### jmx4perl diff --git a/itl/plugins-contrib.d/web.conf b/itl/plugins-contrib.d/web.conf index 62ae886c9..210827c1d 100644 --- a/itl/plugins-contrib.d/web.conf +++ b/itl/plugins-contrib.d/web.conf @@ -587,6 +587,10 @@ object CheckCommand "ssl_cert" { description = "verify that valid DANE records exist (since OpenSSL 1.1.0)" repeat_key = false } + "--ignore-maximum-validity" = { + description = "Ignore the certificate maximum validity" + set_if = "$ssl_cert_ignore_maximum_validity$" + } } From 2ce34e8134b48508f9e19daa7fd82cc848e70826 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Wed, 26 Feb 2025 16:36:57 +0100 Subject: [PATCH 250/415] Fixed double output for timestamps in debug log The timestamps used both in the CheckerComponent and Checkable debug logs were printed in the scientific notation, making them effectively useless. > debug/CheckerComponent: Scheduling info for checkable 'host!service' (2025-02-26 14:53:16 +0100): Object 'host!service', Next Check: 2025-02-26 14:53:16 +0100(1.74058e+09). > debug/Checkable: Update checkable 'host!service' with check interval '300' from last check time at 2025-02-26 14:48:47 +0100 (1.74058e+09) to next check time at 2025-02-26 14:58:12 +0100 (1.74058e+09). Switching to std::fixed actually shows the complete Unix timestamp. > debug/CheckerComponent: Scheduling info for checkable 'host!service' (2025-02-26 15:36:44 +0000): Object 'host!service', Next Check: 2025-02-26 15:36:44 +0000 (1740584204). > debug/Checkable: Update checkable 'host!service' with check interval '60' from last check time at 2025-02-26 15:37:11 +0000 (1740584232) to next check time at 2025-02-26 15:38:09 +0000 (1740584290). --- lib/checker/checkercomponent.cpp | 3 ++- lib/icinga/checkable-check.cpp | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/checker/checkercomponent.cpp b/lib/checker/checkercomponent.cpp index d92101f4e..06ebb7bba 100644 --- a/lib/checker/checkercomponent.cpp +++ b/lib/checker/checkercomponent.cpp @@ -199,7 +199,8 @@ void CheckerComponent::CheckThreadProc() << "Scheduling info for checkable '" << checkable->GetName() << "' (" << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", checkable->GetNextCheck()) << "): Object '" << csi.Object->GetName() << "', Next Check: " - << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", csi.NextCheck) << "(" << csi.NextCheck << ")."; + << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", csi.NextCheck) + << " (" << std::fixed << std::setprecision(0) << csi.NextCheck << ")."; m_PendingCheckables.insert(csi); diff --git a/lib/icinga/checkable-check.cpp b/lib/icinga/checkable-check.cpp index 6e3b8764b..6c9c67c9f 100644 --- a/lib/icinga/checkable-check.cpp +++ b/lib/icinga/checkable-check.cpp @@ -71,9 +71,11 @@ void Checkable::UpdateNextCheck(const MessageOrigin::Ptr& origin) double lastCheck = GetLastCheck(); Log(LogDebug, "Checkable") + << std::fixed << std::setprecision(0) << "Update checkable '" << GetName() << "' with check interval '" << GetCheckInterval() << "' from last check time at " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", (lastCheck < 0 ? 0 : lastCheck)) - << " (" << GetLastCheck() << ") to next check time at " << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", nextCheck) << " (" << nextCheck << ")."; + << " (" << lastCheck << ") to next check time at " + << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", nextCheck) << " (" << nextCheck << ")."; SetNextCheck(nextCheck, false, origin); } From 11ab8690164b76504facf6438e735c941283fbc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Thu, 2 Jan 2025 15:43:13 +0100 Subject: [PATCH 251/415] Bump Boost shipped for Windows to v1.87 --- doc/win-dev.ps1 | 2 +- tools/win32/configure-dev.ps1 | 4 ++-- tools/win32/configure.ps1 | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/win-dev.ps1 b/doc/win-dev.ps1 index f64017bb0..e3b4e5955 100644 --- a/doc/win-dev.ps1 +++ b/doc/win-dev.ps1 @@ -13,7 +13,7 @@ function ThrowOnNativeFailure { $VsVersion = 2019 $MsvcVersion = '14.2' -$BoostVersion = @(1, 86, 0) +$BoostVersion = @(1, 87, 0) $OpensslVersion = '3_0_15' switch ($Env:BITS) { diff --git a/tools/win32/configure-dev.ps1 b/tools/win32/configure-dev.ps1 index a2caeb201..7729dc58f 100644 --- a/tools/win32/configure-dev.ps1 +++ b/tools/win32/configure-dev.ps1 @@ -34,10 +34,10 @@ if (-not (Test-Path env:OPENSSL_ROOT_DIR)) { $env:OPENSSL_ROOT_DIR = 'c:\local\OpenSSL-Win64' } if (-not (Test-Path env:BOOST_ROOT)) { - $env:BOOST_ROOT = 'c:\local\boost_1_86_0' + $env:BOOST_ROOT = 'c:\local\boost_1_87_0' } if (-not (Test-Path env:BOOST_LIBRARYDIR)) { - $env:BOOST_LIBRARYDIR = 'c:\local\boost_1_86_0\lib64-msvc-14.2' + $env:BOOST_LIBRARYDIR = 'c:\local\boost_1_87_0\lib64-msvc-14.2' } if (-not (Test-Path env:FLEX_BINARY)) { $env:FLEX_BINARY = 'C:\ProgramData\chocolatey\bin\win_flex.exe' diff --git a/tools/win32/configure.ps1 b/tools/win32/configure.ps1 index 7ab3dbc52..52d8628a1 100644 --- a/tools/win32/configure.ps1 +++ b/tools/win32/configure.ps1 @@ -36,10 +36,10 @@ if (-not (Test-Path env:OPENSSL_ROOT_DIR)) { $env:OPENSSL_ROOT_DIR = "c:\local\OpenSSL_3_0_15-Win${env:BITS}" } if (-not (Test-Path env:BOOST_ROOT)) { - $env:BOOST_ROOT = "c:\local\boost_1_86_0-Win${env:BITS}" + $env:BOOST_ROOT = "c:\local\boost_1_87_0-Win${env:BITS}" } if (-not (Test-Path env:BOOST_LIBRARYDIR)) { - $env:BOOST_LIBRARYDIR = "c:\local\boost_1_86_0-Win${env:BITS}\lib${env:BITS}-msvc-14.2" + $env:BOOST_LIBRARYDIR = "c:\local\boost_1_87_0-Win${env:BITS}\lib${env:BITS}-msvc-14.2" } if (-not (Test-Path env:FLEX_BINARY)) { $env:FLEX_BINARY = 'C:\ProgramData\chocolatey\bin\win_flex.exe' From 7bd35d8c6b7658170835de0fa623e3a4099569fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Tue, 7 Jan 2025 15:53:20 +0100 Subject: [PATCH 252/415] Don't use boost::asio::ip::tcp::resolver::query It was removed in Boost 1.87. --- lib/base/tcpsocket.hpp | 6 ++---- lib/remote/apilistener.cpp | 4 +--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/lib/base/tcpsocket.hpp b/lib/base/tcpsocket.hpp index 471ad8d23..1cf1a2350 100644 --- a/lib/base/tcpsocket.hpp +++ b/lib/base/tcpsocket.hpp @@ -41,8 +41,7 @@ void Connect(Socket& socket, const String& node, const String& service) using boost::asio::ip::tcp; tcp::resolver resolver (IoEngine::Get().GetIoContext()); - tcp::resolver::query query (node, service); - auto result (resolver.resolve(query)); + auto result (resolver.resolve(node.CStr(), service.CStr())); auto current (result.begin()); for (;;) { @@ -72,8 +71,7 @@ void Connect(Socket& socket, const String& node, const String& service, boost::a using boost::asio::ip::tcp; tcp::resolver resolver (IoEngine::Get().GetIoContext()); - tcp::resolver::query query (node, service); - auto result (resolver.async_resolve(query, yc)); + auto result (resolver.async_resolve(node.CStr(), service.CStr(), yc)); auto current (result.begin()); for (;;) { diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 772cd2df2..6c684e9f1 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -439,9 +439,7 @@ bool ApiListener::AddListener(const String& node, const String& service) try { tcp::resolver resolver (io); - tcp::resolver::query query (node, service, tcp::resolver::query::passive); - - auto result (resolver.resolve(query)); + auto result (resolver.resolve(node.CStr(), service.CStr(), tcp::resolver::passive)); auto current (result.begin()); for (;;) { From 011c67964ee2ab4c9a21f8aab322663e3bf5c317 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Tue, 7 Jan 2025 17:53:42 +0100 Subject: [PATCH 253/415] Don't use boost::asio::io_context::strand method removed in Boost 1.87 --- lib/remote/jsonrpcconnection.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/remote/jsonrpcconnection.cpp b/lib/remote/jsonrpcconnection.cpp index b8ae22aaa..889d4452c 100644 --- a/lib/remote/jsonrpcconnection.cpp +++ b/lib/remote/jsonrpcconnection.cpp @@ -212,7 +212,7 @@ void JsonRpcConnection::SendMessage(const Dictionary::Ptr& message) Ptr keepAlive (this); - m_IoStrand.post([this, keepAlive, message]() { SendMessageInternal(message); }); + boost::asio::post(m_IoStrand, [this, keepAlive, message] { SendMessageInternal(message); }); } void JsonRpcConnection::SendRawMessage(const String& message) @@ -223,7 +223,7 @@ void JsonRpcConnection::SendRawMessage(const String& message) Ptr keepAlive (this); - m_IoStrand.post([this, keepAlive, message]() { + boost::asio::post(m_IoStrand, [this, keepAlive, message] { if (m_ShuttingDown) { return; } From 0662f2b7193693ba1129690ac004af7f866470bb Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 7 Mar 2025 16:22:50 +0100 Subject: [PATCH 254/415] In a coroutine, re-throw everything ex. std::exception (and inheritors) not just boost::coroutines::detail::forced_unwind. This is needed because as of Boost 1.87, boost::asio::spawn() uses Fiber, not Coroutine v1. https://github.com/boostorg/asio/commit/df973a85ed69f021 This is safe because every actual exception shall inherit from std::exception. Except forced_unwind and its Fiber equivalent, so that `catch(const std::exception&)` doesn't catch them and only them. --- lib/base/io-engine.hpp | 13 ++++++---- lib/icingadb/redisconnection.cpp | 41 ++++---------------------------- lib/icingadb/redisconnection.hpp | 8 ++----- 3 files changed, 14 insertions(+), 48 deletions(-) diff --git a/lib/base/io-engine.hpp b/lib/base/io-engine.hpp index 0350d45b8..55a06fb6a 100644 --- a/lib/base/io-engine.hpp +++ b/lib/base/io-engine.hpp @@ -106,14 +106,17 @@ public: try { f(yc); - } catch (const boost::coroutines::detail::forced_unwind &) { - // Required for proper stack unwinding when coroutines are destroyed. - // https://github.com/boostorg/coroutine/issues/39 - throw; } catch (const std::exception& ex) { Log(LogCritical, "IoEngine") << "Exception in coroutine: " << DiagnosticInformation(ex); } catch (...) { - Log(LogCritical, "IoEngine", "Exception in coroutine!"); + try { + Log(LogCritical, "IoEngine", "Exception in coroutine!"); + } catch (...) { + } + + // Required for proper stack unwinding when coroutines are destroyed. + // https://github.com/boostorg/coroutine/issues/39 + throw; } }, boost::coroutines::attributes(GetCoroutineStackSize()) // Set a pre-defined stack size. diff --git a/lib/icingadb/redisconnection.cpp b/lib/icingadb/redisconnection.cpp index a6b82187d..c1f73f5a0 100644 --- a/lib/icingadb/redisconnection.cpp +++ b/lib/icingadb/redisconnection.cpp @@ -377,8 +377,6 @@ void RedisConnection::Connect(asio::yield_context& yc) } break; - } catch (const boost::coroutines::detail::forced_unwind&) { - throw; } catch (const std::exception& ex) { Log(LogCritical, "IcingaDB") << "Cannot connect to " << m_Host << ":" << m_Port << ": " << ex.what(); @@ -408,17 +406,10 @@ void RedisConnection::ReadLoop(asio::yield_context& yc) for (auto i (item.Amount); i; --i) { ReadOne(yc); } - } catch (const boost::coroutines::detail::forced_unwind&) { - throw; } catch (const std::exception& ex) { Log(LogCritical, "IcingaDB") << "Error during receiving the response to a query which has been fired and forgotten: " << ex.what(); - continue; - } catch (...) { - Log(LogCritical, "IcingaDB") - << "Error during receiving the response to a query which has been fired and forgotten"; - continue; } @@ -432,9 +423,7 @@ void RedisConnection::ReadLoop(asio::yield_context& yc) try { reply = ReadOne(yc); - } catch (const boost::coroutines::detail::forced_unwind&) { - throw; - } catch (...) { + } catch (const std::exception&) { promise.set_exception(std::current_exception()); continue; @@ -455,9 +444,7 @@ void RedisConnection::ReadLoop(asio::yield_context& yc) for (auto i (item.Amount); i; --i) { try { replies.emplace_back(ReadOne(yc)); - } catch (const boost::coroutines::detail::forced_unwind&) { - throw; - } catch (...) { + } catch (const std::exception&) { promise.set_exception(std::current_exception()); break; } @@ -551,19 +538,11 @@ void RedisConnection::WriteItem(boost::asio::yield_context& yc, RedisConnection: try { WriteOne(item, yc); - } catch (const boost::coroutines::detail::forced_unwind&) { - throw; } catch (const std::exception& ex) { Log msg (LogCritical, "IcingaDB", "Error during sending query"); LogQuery(item, msg); msg << " which has been fired and forgotten: " << ex.what(); - return; - } catch (...) { - Log msg (LogCritical, "IcingaDB", "Error during sending query"); - LogQuery(item, msg); - msg << " which has been fired and forgotten"; - return; } @@ -587,19 +566,11 @@ void RedisConnection::WriteItem(boost::asio::yield_context& yc, RedisConnection: WriteOne(query, yc); ++i; } - } catch (const boost::coroutines::detail::forced_unwind&) { - throw; } catch (const std::exception& ex) { Log msg (LogCritical, "IcingaDB", "Error during sending query"); LogQuery(item[i], msg); msg << " which has been fired and forgotten: " << ex.what(); - return; - } catch (...) { - Log msg (LogCritical, "IcingaDB", "Error during sending query"); - LogQuery(item[i], msg); - msg << " which has been fired and forgotten"; - return; } @@ -618,9 +589,7 @@ void RedisConnection::WriteItem(boost::asio::yield_context& yc, RedisConnection: try { WriteOne(item.first, yc); - } catch (const boost::coroutines::detail::forced_unwind&) { - throw; - } catch (...) { + } catch (const std::exception&) { item.second.set_exception(std::current_exception()); return; @@ -645,9 +614,7 @@ void RedisConnection::WriteItem(boost::asio::yield_context& yc, RedisConnection: for (auto& query : item.first) { WriteOne(query, yc); } - } catch (const boost::coroutines::detail::forced_unwind&) { - throw; - } catch (...) { + } catch (const std::exception&) { item.second.set_exception(std::current_exception()); return; diff --git a/lib/icingadb/redisconnection.hpp b/lib/icingadb/redisconnection.hpp index 3f963f3d3..acc6e4381 100644 --- a/lib/icingadb/redisconnection.hpp +++ b/lib/icingadb/redisconnection.hpp @@ -389,9 +389,7 @@ RedisConnection::Reply RedisConnection::ReadOne(StreamPtr& stream, boost::asio:: try { return ReadRESP(*strm, yc); - } catch (const boost::coroutines::detail::forced_unwind&) { - throw; - } catch (...) { + } catch (const std::exception&) { if (m_Connecting.exchange(false)) { m_Connected.store(false); stream = nullptr; @@ -427,9 +425,7 @@ void RedisConnection::WriteOne(StreamPtr& stream, RedisConnection::Query& query, try { WriteRESP(*strm, query, yc); strm->async_flush(yc); - } catch (const boost::coroutines::detail::forced_unwind&) { - throw; - } catch (...) { + } catch (const std::exception&) { if (m_Connecting.exchange(false)) { m_Connected.store(false); stream = nullptr; From fb2b2e2d5b32a02309aa1c50ae5e9fb873922988 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 7 Mar 2025 15:47:37 +0100 Subject: [PATCH 255/415] Don't use removed boost::asio::spawn() overload if Boost >= v1.87 --- lib/base/io-engine.hpp | 13 +++++++++++++ test/base-io-engine.cpp | 10 +++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/base/io-engine.hpp b/lib/base/io-engine.hpp index 55a06fb6a..2083b62f8 100644 --- a/lib/base/io-engine.hpp +++ b/lib/base/io-engine.hpp @@ -16,11 +16,16 @@ #include #include #include +#include #include #include #include #include +#if BOOST_VERSION >= 108700 +# include +#endif // BOOST_VERSION >= 108700 + namespace icinga { @@ -102,6 +107,10 @@ public: static void SpawnCoroutine(Handler& h, Function f) { boost::asio::spawn(h, +#if BOOST_VERSION >= 108700 + std::allocator_arg, + boost::context::fixedsize_stack(GetCoroutineStackSize()), +#endif // BOOST_VERSION >= 108700 [f](boost::asio::yield_context yc) { try { @@ -119,7 +128,11 @@ public: throw; } }, +#if BOOST_VERSION >= 108700 + boost::asio::detached +#else // BOOST_VERSION >= 108700 boost::coroutines::attributes(GetCoroutineStackSize()) // Set a pre-defined stack size. +#endif // BOOST_VERSION >= 108700 ); } diff --git a/test/base-io-engine.cpp b/test/base-io-engine.cpp index 869688b1a..3a251b1b4 100644 --- a/test/base-io-engine.cpp +++ b/test/base-io-engine.cpp @@ -17,7 +17,7 @@ BOOST_AUTO_TEST_CASE(timeout_run) boost::asio::io_context::strand strand (io); int called = 0; - boost::asio::spawn(strand, [&](boost::asio::yield_context yc) { + IoEngine::SpawnCoroutine(strand, [&](boost::asio::yield_context yc) { boost::asio::deadline_timer timer (io); Timeout timeout (strand, boost::posix_time::millisec(300), [&called] { ++called; }); @@ -44,7 +44,7 @@ BOOST_AUTO_TEST_CASE(timeout_cancelled) boost::asio::io_context::strand strand (io); int called = 0; - boost::asio::spawn(strand, [&](boost::asio::yield_context yc) { + IoEngine::SpawnCoroutine(strand, [&](boost::asio::yield_context yc) { boost::asio::deadline_timer timer (io); Timeout timeout (strand, boost::posix_time::millisec(300), [&called] { ++called; }); @@ -71,7 +71,7 @@ BOOST_AUTO_TEST_CASE(timeout_scope) boost::asio::io_context::strand strand (io); int called = 0; - boost::asio::spawn(strand, [&](boost::asio::yield_context yc) { + IoEngine::SpawnCoroutine(strand, [&](boost::asio::yield_context yc) { boost::asio::deadline_timer timer (io); { @@ -100,7 +100,7 @@ BOOST_AUTO_TEST_CASE(timeout_due_cancelled) boost::asio::io_context::strand strand (io); int called = 0; - boost::asio::spawn(strand, [&](boost::asio::yield_context yc) { + IoEngine::SpawnCoroutine(strand, [&](boost::asio::yield_context yc) { boost::asio::deadline_timer timer (io); Timeout timeout (strand, boost::posix_time::millisec(300), [&called] { ++called; }); @@ -131,7 +131,7 @@ BOOST_AUTO_TEST_CASE(timeout_due_scope) boost::asio::io_context::strand strand (io); int called = 0; - boost::asio::spawn(strand, [&](boost::asio::yield_context yc) { + IoEngine::SpawnCoroutine(strand, [&](boost::asio::yield_context yc) { boost::asio::deadline_timer timer (io); { From ccfc72267f5c4bddad6779bb30c93a504f286500 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Fri, 11 Apr 2025 12:27:25 +0200 Subject: [PATCH 256/415] Prefer icinga::String::GetData() over icinga::String::CStr() Creating the string_view from the std::string (as returned by GetData()) uses the stored length instead of having to detect it by finding '\0'. --- lib/base/tcpsocket.hpp | 4 ++-- lib/remote/apilistener.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/base/tcpsocket.hpp b/lib/base/tcpsocket.hpp index 1cf1a2350..7e64d57a5 100644 --- a/lib/base/tcpsocket.hpp +++ b/lib/base/tcpsocket.hpp @@ -41,7 +41,7 @@ void Connect(Socket& socket, const String& node, const String& service) using boost::asio::ip::tcp; tcp::resolver resolver (IoEngine::Get().GetIoContext()); - auto result (resolver.resolve(node.CStr(), service.CStr())); + auto result (resolver.resolve(node.GetData(), service.GetData())); auto current (result.begin()); for (;;) { @@ -71,7 +71,7 @@ void Connect(Socket& socket, const String& node, const String& service, boost::a using boost::asio::ip::tcp; tcp::resolver resolver (IoEngine::Get().GetIoContext()); - auto result (resolver.async_resolve(node.CStr(), service.CStr(), yc)); + auto result (resolver.async_resolve(node.GetData(), service.GetData(), yc)); auto current (result.begin()); for (;;) { diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 6c684e9f1..6bcf5bb5f 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -439,7 +439,7 @@ bool ApiListener::AddListener(const String& node, const String& service) try { tcp::resolver resolver (io); - auto result (resolver.resolve(node.CStr(), service.CStr(), tcp::resolver::passive)); + auto result (resolver.resolve(node.GetData(), service.GetData(), tcp::resolver::passive)); auto current (result.begin()); for (;;) { From d1d399f8b329dbe3f6f1f172ece165332fb9512f Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Fri, 11 Apr 2025 13:05:21 +0200 Subject: [PATCH 257/415] Avoid multiple #if in a single function call expression Simply giving two entire call expressions for either Boost version greatly improves readability in my opinion. --- lib/base/io-engine.hpp | 43 ++++++++++++++++++++---------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/lib/base/io-engine.hpp b/lib/base/io-engine.hpp index 2083b62f8..2d2c5e922 100644 --- a/lib/base/io-engine.hpp +++ b/lib/base/io-engine.hpp @@ -105,35 +105,32 @@ public: template static void SpawnCoroutine(Handler& h, Function f) { - - boost::asio::spawn(h, -#if BOOST_VERSION >= 108700 - std::allocator_arg, - boost::context::fixedsize_stack(GetCoroutineStackSize()), -#endif // BOOST_VERSION >= 108700 - [f](boost::asio::yield_context yc) { - + auto wrapper = [f](boost::asio::yield_context yc) { + try { + f(yc); + } catch (const std::exception& ex) { + Log(LogCritical, "IoEngine") << "Exception in coroutine: " << DiagnosticInformation(ex); + } catch (...) { try { - f(yc); - } catch (const std::exception& ex) { - Log(LogCritical, "IoEngine") << "Exception in coroutine: " << DiagnosticInformation(ex); + Log(LogCritical, "IoEngine", "Exception in coroutine!"); } catch (...) { - try { - Log(LogCritical, "IoEngine", "Exception in coroutine!"); - } catch (...) { - } - - // Required for proper stack unwinding when coroutines are destroyed. - // https://github.com/boostorg/coroutine/issues/39 - throw; } - }, + + // Required for proper stack unwinding when coroutines are destroyed. + // https://github.com/boostorg/coroutine/issues/39 + throw; + } + }; + #if BOOST_VERSION >= 108700 + boost::asio::spawn(h, + std::allocator_arg, boost::context::fixedsize_stack(GetCoroutineStackSize()), + std::move(wrapper), boost::asio::detached -#else // BOOST_VERSION >= 108700 - boost::coroutines::attributes(GetCoroutineStackSize()) // Set a pre-defined stack size. -#endif // BOOST_VERSION >= 108700 ); +#else // BOOST_VERSION >= 108700 + boost::asio::spawn(h, std::move(wrapper), boost::coroutines::attributes(GetCoroutineStackSize())); +#endif // BOOST_VERSION >= 108700 } static inline From d3fae440d4b229581c39bc972ea2f3edf0d23efe Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Tue, 15 Apr 2025 15:10:12 +0200 Subject: [PATCH 258/415] SpawnCoroutine: move callback into wrapper lambda MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit f isn't used otherwise in the function, so if possible, it can just be moved into the lambda, avoiding a copy. Co-authored-by: Alexander Aleksandrovič Klimov --- lib/base/io-engine.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/base/io-engine.hpp b/lib/base/io-engine.hpp index 2d2c5e922..64831ff8c 100644 --- a/lib/base/io-engine.hpp +++ b/lib/base/io-engine.hpp @@ -105,7 +105,7 @@ public: template static void SpawnCoroutine(Handler& h, Function f) { - auto wrapper = [f](boost::asio::yield_context yc) { + auto wrapper = [f = std::move(f)](boost::asio::yield_context yc) { try { f(yc); } catch (const std::exception& ex) { From c2ddd20ef379ed5d5a90c239bfe042b68a5d8738 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 31 Mar 2023 12:36:37 +0200 Subject: [PATCH 259/415] Fix compiler warnings by (copy-)constructing loop variables explicitly for (const T& needle : haystack) creates the illusion that haystack is a container of T and we're just borrowing needle. In these cases that's not true. --- icinga-app/icinga.cpp | 4 ++-- lib/base/scriptutils.cpp | 6 +++--- lib/cli/daemonutility.cpp | 2 +- lib/cli/nodeutility.cpp | 4 ++-- lib/cli/nodewizardcommand.cpp | 2 +- lib/db_ido/hostdbobject.cpp | 2 +- lib/db_ido/servicedbobject.cpp | 2 +- lib/db_ido/userdbobject.cpp | 2 +- lib/icinga/clusterevents.cpp | 2 +- lib/icinga/comment.cpp | 4 ++-- lib/icinga/compatutility.cpp | 2 +- lib/icinga/downtime.cpp | 6 +++--- lib/icinga/host.cpp | 4 ++-- lib/icinga/hostgroup.cpp | 2 +- lib/icinga/legacytimeperiod.cpp | 4 ++-- lib/icinga/notification.cpp | 4 ++-- lib/icinga/service.cpp | 2 +- lib/icinga/servicegroup.cpp | 2 +- lib/icinga/timeperiod.cpp | 20 ++++++++++---------- lib/icinga/user.cpp | 4 ++-- lib/icinga/usergroup.cpp | 2 +- lib/livestatus/attributefilter.cpp | 2 +- lib/livestatus/statehisttable.cpp | 2 +- lib/remote/actionshandler.cpp | 4 ++-- lib/remote/deleteobjecthandler.cpp | 2 +- lib/remote/eventshandler.cpp | 4 ++-- lib/remote/filterutility.cpp | 2 +- lib/remote/httphandler.cpp | 2 +- lib/remote/modifyobjecthandler.cpp | 2 +- lib/remote/objectqueryhandler.cpp | 10 +++++----- lib/remote/typequeryhandler.cpp | 2 +- lib/remote/url.cpp | 4 ++-- lib/remote/variablequeryhandler.cpp | 2 +- lib/remote/zone.cpp | 4 ++-- plugins/check_nscp_api.cpp | 4 ++-- test/icinga-legacytimeperiod.cpp | 4 ++-- 36 files changed, 66 insertions(+), 66 deletions(-) diff --git a/icinga-app/icinga.cpp b/icinga-app/icinga.cpp index 1811c8e07..d4e27c566 100644 --- a/icinga-app/icinga.cpp +++ b/icinga-app/icinga.cpp @@ -401,7 +401,7 @@ static int Main() #endif /* _WIN32 */ if (vm.count("define")) { - for (const String& define : vm["define"].as >()) { + for (String define : vm["define"].as>()) { String key, value; size_t pos = define.FindFirstOf('='); if (pos != String::NPos) { @@ -460,7 +460,7 @@ static int Main() ConfigCompiler::AddIncludeSearchDir(Configuration::IncludeConfDir); if (!autocomplete && vm.count("include")) { - for (const String& includePath : vm["include"].as >()) { + for (String includePath : vm["include"].as>()) { ConfigCompiler::AddIncludeSearchDir(includePath); } } diff --git a/lib/base/scriptutils.cpp b/lib/base/scriptutils.cpp index 3611799ff..9054dadd6 100644 --- a/lib/base/scriptutils.cpp +++ b/lib/base/scriptutils.cpp @@ -124,7 +124,7 @@ bool ScriptUtils::Regex(const std::vector& args) if (texts->GetLength() == 0) return false; - for (const String& text : texts) { + for (String text : texts) { bool res = false; try { boost::smatch what; @@ -177,7 +177,7 @@ bool ScriptUtils::Match(const std::vector& args) if (texts->GetLength() == 0) return false; - for (const String& text : texts) { + for (String text : texts) { bool res = Utility::Match(pattern, text); if (mode == MatchAny && res) @@ -223,7 +223,7 @@ bool ScriptUtils::CidrMatch(const std::vector& args) if (ips->GetLength() == 0) return false; - for (const String& ip : ips) { + for (String ip : ips) { bool res = Utility::CidrMatch(pattern, ip); if (mode == MatchAny && res) diff --git a/lib/cli/daemonutility.cpp b/lib/cli/daemonutility.cpp index 5e250d22c..3116f8ca4 100644 --- a/lib/cli/daemonutility.cpp +++ b/lib/cli/daemonutility.cpp @@ -121,7 +121,7 @@ bool DaemonUtility::ValidateConfigFiles(const std::vector& configs, ConfigCompilerContext::GetInstance()->OpenObjectsFile(objectsFile); if (!configs.empty()) { - for (const String& configPath : configs) { + for (String configPath : configs) { try { std::unique_ptr expression = ConfigCompiler::CompileFile(configPath, String(), "_etc"); success = ExecuteExpression(&*expression); diff --git a/lib/cli/nodeutility.cpp b/lib/cli/nodeutility.cpp index 523532a24..e2986c3ee 100644 --- a/lib/cli/nodeutility.cpp +++ b/lib/cli/nodeutility.cpp @@ -47,7 +47,7 @@ int NodeUtility::GenerateNodeIcingaConfig(const String& endpointName, const Stri Array::Ptr myParentZoneMembers = new Array(); - for (const String& endpoint : endpoints) { + for (String endpoint : endpoints) { /* extract all --endpoint arguments and store host,port info */ std::vector tokens = endpoint.Split(","); @@ -170,7 +170,7 @@ bool NodeUtility::WriteNodeConfigObjects(const String& filename, const Array::Pt fp << " */\n\n"; ObjectLock olock(objects); - for (const Dictionary::Ptr& object : objects) { + for (Dictionary::Ptr object : objects) { SerializeObject(fp, object); } diff --git a/lib/cli/nodewizardcommand.cpp b/lib/cli/nodewizardcommand.cpp index 3a3cd42bd..c6874a5af 100644 --- a/lib/cli/nodewizardcommand.cpp +++ b/lib/cli/nodewizardcommand.cpp @@ -242,7 +242,7 @@ wizard_endpoint_loop_start: /* Extract parent node information. */ String parentHost, parentPort; - for (const String& endpoint : endpoints) { + for (String endpoint : endpoints) { std::vector tokens = endpoint.Split(","); if (tokens.size() > 1) diff --git a/lib/db_ido/hostdbobject.cpp b/lib/db_ido/hostdbobject.cpp index 627b9b74e..3f38683b6 100644 --- a/lib/db_ido/hostdbobject.cpp +++ b/lib/db_ido/hostdbobject.cpp @@ -161,7 +161,7 @@ void HostDbObject::OnConfigUpdateHeavy() if (groups) { ObjectLock olock(groups); - for (const String& groupName : groups) { + for (String groupName : groups) { HostGroup::Ptr group = HostGroup::GetByName(groupName); DbQuery query2; diff --git a/lib/db_ido/servicedbobject.cpp b/lib/db_ido/servicedbobject.cpp index 6d0701111..b221845a8 100644 --- a/lib/db_ido/servicedbobject.cpp +++ b/lib/db_ido/servicedbobject.cpp @@ -159,7 +159,7 @@ void ServiceDbObject::OnConfigUpdateHeavy() if (groups) { ObjectLock olock(groups); - for (const String& groupName : groups) { + for (String groupName : groups) { ServiceGroup::Ptr group = ServiceGroup::GetByName(groupName); DbQuery query2; diff --git a/lib/db_ido/userdbobject.cpp b/lib/db_ido/userdbobject.cpp index 439b8fb05..74c1284b0 100644 --- a/lib/db_ido/userdbobject.cpp +++ b/lib/db_ido/userdbobject.cpp @@ -79,7 +79,7 @@ void UserDbObject::OnConfigUpdateHeavy() if (groups) { ObjectLock olock(groups); - for (const String& groupName : groups) { + for (String groupName : groups) { UserGroup::Ptr group = UserGroup::GetByName(groupName); DbQuery query2; diff --git a/lib/icinga/clusterevents.cpp b/lib/icinga/clusterevents.cpp index b49d2071d..87821dcea 100644 --- a/lib/icinga/clusterevents.cpp +++ b/lib/icinga/clusterevents.cpp @@ -1358,7 +1358,7 @@ Value ClusterEvents::NotificationSentToAllUsersAPIHandler(const MessageOrigin::P { ObjectLock olock(ausers); - for (const String& auser : ausers) { + for (String auser : ausers) { User::Ptr user = User::GetByName(auser); if (!user) diff --git a/lib/icinga/comment.cpp b/lib/icinga/comment.cpp index 846735262..449fe2334 100644 --- a/lib/icinga/comment.cpp +++ b/lib/icinga/comment.cpp @@ -169,7 +169,7 @@ String Comment::AddComment(const Checkable::Ptr& checkable, CommentType entryTyp if (!ConfigObjectUtility::CreateObject(Comment::TypeInstance, fullName, config, errors, nullptr)) { ObjectLock olock(errors); - for (const String& error : errors) { + for (String error : errors) { Log(LogCritical, "Comment", error); } @@ -206,7 +206,7 @@ void Comment::RemoveComment(const String& id, bool removedManually, const String if (!ConfigObjectUtility::DeleteObject(comment, false, errors, nullptr)) { ObjectLock olock(errors); - for (const String& error : errors) { + for (String error : errors) { Log(LogCritical, "Comment", error); } diff --git a/lib/icinga/compatutility.cpp b/lib/icinga/compatutility.cpp index 95aed4396..a985b90f5 100644 --- a/lib/icinga/compatutility.cpp +++ b/lib/icinga/compatutility.cpp @@ -25,7 +25,7 @@ String CompatUtility::GetCommandLine(const Command::Ptr& command) Array::Ptr args = commandLine; ObjectLock olock(args); - for (const String& arg : args) { + for (String arg : args) { // This is obviously incorrect for non-trivial cases. result += " \"" + EscapeString(arg) + "\""; } diff --git a/lib/icinga/downtime.cpp b/lib/icinga/downtime.cpp index 98a65d417..66462cef6 100644 --- a/lib/icinga/downtime.cpp +++ b/lib/icinga/downtime.cpp @@ -330,7 +330,7 @@ Downtime::Ptr Downtime::AddDowntime(const Checkable::Ptr& checkable, const Strin if (!ConfigObjectUtility::CreateObject(Downtime::TypeInstance, fullName, config, errors, nullptr)) { ObjectLock olock(errors); - for (const String& error : errors) { + for (String error : errors) { Log(LogCritical, "Downtime", error); } @@ -388,7 +388,7 @@ void Downtime::RemoveDowntime(const String& id, bool includeChildren, DowntimeRe if (!ConfigObjectUtility::DeleteObject(downtime, false, errors, nullptr)) { ObjectLock olock(errors); - for (const String& error : errors) { + for (String error : errors) { Log(LogCritical, "Downtime", error); } @@ -505,7 +505,7 @@ void Downtime::TriggerDowntime(double triggerTime) { ObjectLock olock(triggers); - for (const String& triggerName : triggers) { + for (String triggerName : triggers) { Downtime::Ptr downtime = Downtime::GetByName(triggerName); if (!downtime) diff --git a/lib/icinga/host.cpp b/lib/icinga/host.cpp index 22dd79b40..5bc0cead7 100644 --- a/lib/icinga/host.cpp +++ b/lib/icinga/host.cpp @@ -38,7 +38,7 @@ void Host::OnAllConfigLoaded() ObjectLock olock(groups); - for (const String& name : groups) { + for (String name : groups) { HostGroup::Ptr hg = HostGroup::GetByName(name); if (hg) @@ -71,7 +71,7 @@ void Host::Stop(bool runtimeRemoved) if (groups) { ObjectLock olock(groups); - for (const String& name : groups) { + for (String name : groups) { HostGroup::Ptr hg = HostGroup::GetByName(name); if (hg) diff --git a/lib/icinga/hostgroup.cpp b/lib/icinga/hostgroup.cpp index a22f3b74c..9ab338300 100644 --- a/lib/icinga/hostgroup.cpp +++ b/lib/icinga/hostgroup.cpp @@ -91,7 +91,7 @@ bool HostGroup::ResolveGroupMembership(const Host::Ptr& host, bool add, int rsta if (groups && groups->GetLength() > 0) { ObjectLock olock(groups); - for (const String& name : groups) { + for (String name : groups) { HostGroup::Ptr group = HostGroup::GetByName(name); if (group && !group->ResolveGroupMembership(host, add, rstack + 1)) diff --git a/lib/icinga/legacytimeperiod.cpp b/lib/icinga/legacytimeperiod.cpp index 55bc58ed3..059350ad4 100644 --- a/lib/icinga/legacytimeperiod.cpp +++ b/lib/icinga/legacytimeperiod.cpp @@ -497,7 +497,7 @@ Dictionary::Ptr LegacyTimePeriod::FindRunningSegment(const String& daydef, const double bestEnd = 0.0; ObjectLock olock(segments); - for (const Dictionary::Ptr& segment : segments) { + for (Dictionary::Ptr segment : segments) { double begin = segment->Get("begin"); double end = segment->Get("end"); @@ -555,7 +555,7 @@ Dictionary::Ptr LegacyTimePeriod::FindNextSegment(const String& daydef, const St double bestBegin; ObjectLock olock(segments); - for (const Dictionary::Ptr& segment : segments) { + for (Dictionary::Ptr segment : segments) { double begin = segment->Get("begin"); if (begin < tsref) diff --git a/lib/icinga/notification.cpp b/lib/icinga/notification.cpp index bd3158ced..b8cb62ff0 100644 --- a/lib/icinga/notification.cpp +++ b/lib/icinga/notification.cpp @@ -173,7 +173,7 @@ std::set Notification::GetUsers() const if (users) { ObjectLock olock(users); - for (const String& name : users) { + for (String name : users) { User::Ptr user = User::GetByName(name); if (!user) @@ -195,7 +195,7 @@ std::set Notification::GetUserGroups() const if (groups) { ObjectLock olock(groups); - for (const String& name : groups) { + for (String name : groups) { UserGroup::Ptr ug = UserGroup::GetByName(name); if (!ug) diff --git a/lib/icinga/service.cpp b/lib/icinga/service.cpp index c24647c82..a26512b77 100644 --- a/lib/icinga/service.cpp +++ b/lib/icinga/service.cpp @@ -65,7 +65,7 @@ void Service::OnAllConfigLoaded() ObjectLock olock(groups); - for (const String& name : groups) { + for (String name : groups) { ServiceGroup::Ptr sg = ServiceGroup::GetByName(name); if (sg) diff --git a/lib/icinga/servicegroup.cpp b/lib/icinga/servicegroup.cpp index d21f852b9..ee2bc9c6e 100644 --- a/lib/icinga/servicegroup.cpp +++ b/lib/icinga/servicegroup.cpp @@ -94,7 +94,7 @@ bool ServiceGroup::ResolveGroupMembership(const Service::Ptr& service, bool add, if (groups && groups->GetLength() > 0) { ObjectLock olock(groups); - for (const String& name : groups) { + for (String name : groups) { ServiceGroup::Ptr group = ServiceGroup::GetByName(name); if (group && !group->ResolveGroupMembership(service, add, rstack + 1)) diff --git a/lib/icinga/timeperiod.cpp b/lib/icinga/timeperiod.cpp index e305b80ad..d5188a68b 100644 --- a/lib/icinga/timeperiod.cpp +++ b/lib/icinga/timeperiod.cpp @@ -57,7 +57,7 @@ void TimePeriod::AddSegment(double begin, double end) if (segments) { /* Try to merge the new segment into an existing segment. */ ObjectLock dlock(segments); - for (const Dictionary::Ptr& segment : segments) { + for (Dictionary::Ptr segment : segments) { if (segment->Get("begin") <= begin && segment->Get("end") >= end) return; /* New segment is fully contained in this segment. */ @@ -122,7 +122,7 @@ void TimePeriod::RemoveSegment(double begin, double end) /* Try to split or adjust an existing segment. */ ObjectLock dlock(segments); - for (const Dictionary::Ptr& segment : segments) { + for (Dictionary::Ptr segment : segments) { /* Fully contained in the specified range? */ if (segment->Get("begin") >= begin && segment->Get("end") <= end) // Don't add the old segment, because the segment is fully contained into our range @@ -193,7 +193,7 @@ void TimePeriod::PurgeSegments(double end) /* Remove old segments. */ ObjectLock dlock(segments); - for (const Dictionary::Ptr& segment : segments) { + for (Dictionary::Ptr segment : segments) { if (segment->Get("end") >= end) newSegments->Add(segment); } @@ -212,7 +212,7 @@ void TimePeriod::Merge(const TimePeriod::Ptr& timeperiod, bool include) if (segments) { ObjectLock dlock(segments); ObjectLock ilock(this); - for (const Dictionary::Ptr& segment : segments) { + for (Dictionary::Ptr segment : segments) { include ? AddSegment(segment) : RemoveSegment(segment); } } @@ -239,7 +239,7 @@ void TimePeriod::UpdateRegion(double begin, double end, bool clearExisting) if (segments) { ObjectLock dlock(segments); - for (const Dictionary::Ptr& segment : segments) { + for (Dictionary::Ptr segment : segments) { AddSegment(segment); } } @@ -252,7 +252,7 @@ void TimePeriod::UpdateRegion(double begin, double end, bool clearExisting) if (timeranges) { ObjectLock olock(timeranges); - for (const String& name : timeranges) { + for (String name : timeranges) { const TimePeriod::Ptr timeperiod = TimePeriod::GetByName(name); if (timeperiod) @@ -265,7 +265,7 @@ void TimePeriod::UpdateRegion(double begin, double end, bool clearExisting) if (timeranges) { ObjectLock olock(timeranges); - for (const String& name : timeranges) { + for (String name : timeranges) { const TimePeriod::Ptr timeperiod = TimePeriod::GetByName(name); if (timeperiod) @@ -290,7 +290,7 @@ bool TimePeriod::IsInside(double ts) const if (segments) { ObjectLock dlock(segments); - for (const Dictionary::Ptr& segment : segments) { + for (Dictionary::Ptr segment : segments) { if (ts >= segment->Get("begin") && ts < segment->Get("end")) return true; } @@ -309,7 +309,7 @@ double TimePeriod::FindNextTransition(double begin) if (segments) { ObjectLock dlock(segments); - for (const Dictionary::Ptr& segment : segments) { + for (Dictionary::Ptr segment : segments) { if (segment->Get("begin") > begin && (segment->Get("begin") < closestTransition || closestTransition == -1)) closestTransition = segment->Get("begin"); @@ -360,7 +360,7 @@ void TimePeriod::Dump() if (segments) { ObjectLock dlock(segments); - for (const Dictionary::Ptr& segment : segments) { + for (Dictionary::Ptr segment : segments) { Log(LogDebug, "TimePeriod") << "Segment: " << Utility::FormatDateTime("%c", segment->Get("begin")) << " <-> " << Utility::FormatDateTime("%c", segment->Get("end")); diff --git a/lib/icinga/user.cpp b/lib/icinga/user.cpp index 4d99db7a1..5c646f656 100644 --- a/lib/icinga/user.cpp +++ b/lib/icinga/user.cpp @@ -33,7 +33,7 @@ void User::OnAllConfigLoaded() ObjectLock olock(groups); - for (const String& name : groups) { + for (String name : groups) { UserGroup::Ptr ug = UserGroup::GetByName(name); if (ug) @@ -51,7 +51,7 @@ void User::Stop(bool runtimeRemoved) if (groups) { ObjectLock olock(groups); - for (const String& name : groups) { + for (String name : groups) { UserGroup::Ptr ug = UserGroup::GetByName(name); if (ug) diff --git a/lib/icinga/usergroup.cpp b/lib/icinga/usergroup.cpp index 1ae89af88..043d2bc8d 100644 --- a/lib/icinga/usergroup.cpp +++ b/lib/icinga/usergroup.cpp @@ -110,7 +110,7 @@ bool UserGroup::ResolveGroupMembership(const User::Ptr& user, bool add, int rsta if (groups && groups->GetLength() > 0) { ObjectLock olock(groups); - for (const String& name : groups) { + for (String name : groups) { UserGroup::Ptr group = UserGroup::GetByName(name); if (group && !group->ResolveGroupMembership(user, add, rstack + 1)) diff --git a/lib/livestatus/attributefilter.cpp b/lib/livestatus/attributefilter.cpp index 50d7244eb..a27c7d7af 100644 --- a/lib/livestatus/attributefilter.cpp +++ b/lib/livestatus/attributefilter.cpp @@ -27,7 +27,7 @@ bool AttributeFilter::Apply(const Table::Ptr& table, const Value& row) bool negate = (m_Operator == "<"); ObjectLock olock(array); - for (const String& item : array) { + for (String item : array) { if (item == m_Operand) return !negate; /* Item found in list. */ } diff --git a/lib/livestatus/statehisttable.cpp b/lib/livestatus/statehisttable.cpp index 2d7e49be4..46a6675bb 100644 --- a/lib/livestatus/statehisttable.cpp +++ b/lib/livestatus/statehisttable.cpp @@ -252,7 +252,7 @@ void StateHistTable::FetchRows(const AddRowFunction& addRowFn) Checkable::Ptr checkable; for (const auto& kv : m_CheckablesCache) { - for (const Dictionary::Ptr& state_hist_bag : kv.second) { + for (Dictionary::Ptr state_hist_bag : kv.second) { /* pass a dictionary from state history array */ if (!addRowFn(state_hist_bag, LivestatusGroupByNone, Empty)) return; diff --git a/lib/remote/actionshandler.cpp b/lib/remote/actionshandler.cpp index 016c76d57..cd16c2bc0 100644 --- a/lib/remote/actionshandler.cpp +++ b/lib/remote/actionshandler.cpp @@ -88,7 +88,7 @@ bool ActionsHandler::HandleRequest( if (params) verbose = HttpUtility::GetLastParameter(params, "verbose"); - for (const ConfigObject::Ptr& obj : objs) { + for (ConfigObject::Ptr obj : objs) { try { results.emplace_back(action->Invoke(obj, params)); } catch (const std::exception& ex) { @@ -108,7 +108,7 @@ bool ActionsHandler::HandleRequest( int statusCode = 500; std::set okStatusCodes, nonOkStatusCodes; - for (const Dictionary::Ptr& res : results) { + for (Dictionary::Ptr res : results) { if (!res->Contains("code")) { continue; } diff --git a/lib/remote/deleteobjecthandler.cpp b/lib/remote/deleteobjecthandler.cpp index 6a7d194d4..150de99e0 100644 --- a/lib/remote/deleteobjecthandler.cpp +++ b/lib/remote/deleteobjecthandler.cpp @@ -78,7 +78,7 @@ bool DeleteObjectHandler::HandleRequest( bool success = true; - for (const ConfigObject::Ptr& obj : objs) { + for (ConfigObject::Ptr obj : objs) { int code; String status; Array::Ptr errors = new Array(); diff --git a/lib/remote/eventshandler.cpp b/lib/remote/eventshandler.cpp index bdda71461..897398d4a 100644 --- a/lib/remote/eventshandler.cpp +++ b/lib/remote/eventshandler.cpp @@ -73,7 +73,7 @@ bool EventsHandler::HandleRequest( { ObjectLock olock(types); - for (const String& type : types) { + for (String type : types) { FilterUtility::CheckPermission(user, "events/" + type); } } @@ -89,7 +89,7 @@ bool EventsHandler::HandleRequest( { ObjectLock olock(types); - for (const String& type : types) { + for (String type : types) { auto typeId (l_EventTypes.find(type)); if (typeId != l_EventTypes.end()) { diff --git a/lib/remote/filterutility.cpp b/lib/remote/filterutility.cpp index d5dc2b6ba..788a97b2e 100644 --- a/lib/remote/filterutility.cpp +++ b/lib/remote/filterutility.cpp @@ -241,7 +241,7 @@ std::vector FilterUtility::GetFilterTargets(const QueryDescription& qd, c Array::Ptr names = query->Get(attr); if (names) { ObjectLock olock(names); - for (const String& name : names) { + for (String name : names) { Object::Ptr target = provider->GetTargetByName(type, name); if (!FilterUtility::EvaluateFilter(permissionFrame, permissionFilter.get(), target, variableName)) diff --git a/lib/remote/httphandler.cpp b/lib/remote/httphandler.cpp index 71a89e4ce..f67df4c69 100644 --- a/lib/remote/httphandler.cpp +++ b/lib/remote/httphandler.cpp @@ -66,7 +66,7 @@ void HttpHandler::ProcessRequest( if (current_handlers) { ObjectLock olock(current_handlers); - for (const HttpHandler::Ptr& current_handler : current_handlers) { + for (HttpHandler::Ptr current_handler : current_handlers) { handlers.push_back(current_handler); } } diff --git a/lib/remote/modifyobjecthandler.cpp b/lib/remote/modifyobjecthandler.cpp index a817faad8..dabe69523 100644 --- a/lib/remote/modifyobjecthandler.cpp +++ b/lib/remote/modifyobjecthandler.cpp @@ -104,7 +104,7 @@ bool ModifyObjectHandler::HandleRequest( ArrayData results; - for (const ConfigObject::Ptr& obj : objs) { + for (ConfigObject::Ptr obj : objs) { Dictionary::Ptr result1 = new Dictionary(); result1->Set("type", type->GetName()); diff --git a/lib/remote/objectqueryhandler.cpp b/lib/remote/objectqueryhandler.cpp index 06d168b8a..fbd5c7e70 100644 --- a/lib/remote/objectqueryhandler.cpp +++ b/lib/remote/objectqueryhandler.cpp @@ -23,7 +23,7 @@ Dictionary::Ptr ObjectQueryHandler::SerializeObjectAttrs(const Object::Ptr& obje if (isJoin && attrs) { ObjectLock olock(attrs); - for (const String& attr : attrs) { + for (String attr : attrs) { if (attr == attrPrefix) { allAttrs = true; break; @@ -40,7 +40,7 @@ Dictionary::Ptr ObjectQueryHandler::SerializeObjectAttrs(const Object::Ptr& obje } } else if (attrs) { ObjectLock olock(attrs); - for (const String& attr : attrs) { + for (String attr : attrs) { String userAttr; if (isJoin) { @@ -173,7 +173,7 @@ bool ObjectQueryHandler::HandleRequest( if (ujoins) { ObjectLock olock(ujoins); - for (const String& ujoin : ujoins) { + for (String ujoin : ujoins) { userJoinAttrs.insert(ujoin.SubStr(0, ujoin.FindFirstOf("."))); } } @@ -193,7 +193,7 @@ bool ObjectQueryHandler::HandleRequest( std::unordered_map>> typePermissions; std::unordered_map objectAccessAllowed; - for (const ConfigObject::Ptr& obj : objs) { + for (ConfigObject::Ptr obj : objs) { DictionaryData result1{ { "name", obj->GetName() }, { "type", obj->GetReflectionType()->GetName() } @@ -203,7 +203,7 @@ bool ObjectQueryHandler::HandleRequest( if (umetas) { ObjectLock olock(umetas); - for (const String& meta : umetas) { + for (String meta : umetas) { if (meta == "used_by") { Array::Ptr used_by = new Array(); metaAttrs.emplace_back("used_by", used_by); diff --git a/lib/remote/typequeryhandler.cpp b/lib/remote/typequeryhandler.cpp index 4e8265398..b30dbb14a 100644 --- a/lib/remote/typequeryhandler.cpp +++ b/lib/remote/typequeryhandler.cpp @@ -91,7 +91,7 @@ bool TypeQueryHandler::HandleRequest( ArrayData results; - for (const Type::Ptr& obj : objs) { + for (Type::Ptr obj : objs) { Dictionary::Ptr result1 = new Dictionary(); results.push_back(result1); diff --git a/lib/remote/url.cpp b/lib/remote/url.cpp index bf5b9ed24..24a573184 100644 --- a/lib/remote/url.cpp +++ b/lib/remote/url.cpp @@ -290,7 +290,7 @@ bool Url::ParsePath(const String& path) boost::char_separator sep("/"); boost::tokenizer > tokens(pathStr, sep); - for (const String& token : tokens) { + for (String token : tokens) { if (token.IsEmpty()) continue; @@ -310,7 +310,7 @@ bool Url::ParseQuery(const String& query) boost::char_separator sep("&"); boost::tokenizer > tokens(queryStr, sep); - for (const String& token : tokens) { + for (String token : tokens) { size_t pHelper = token.Find("="); if (pHelper == 0) diff --git a/lib/remote/variablequeryhandler.cpp b/lib/remote/variablequeryhandler.cpp index f17a9f5f2..7264338cb 100644 --- a/lib/remote/variablequeryhandler.cpp +++ b/lib/remote/variablequeryhandler.cpp @@ -98,7 +98,7 @@ bool VariableQueryHandler::HandleRequest( ArrayData results; - for (const Dictionary::Ptr& var : objs) { + for (Dictionary::Ptr var : objs) { if (var->Get("name") == "TicketSalt") continue; diff --git a/lib/remote/zone.cpp b/lib/remote/zone.cpp index 5ae1468c1..84838e488 100644 --- a/lib/remote/zone.cpp +++ b/lib/remote/zone.cpp @@ -27,7 +27,7 @@ void Zone::OnAllConfigLoaded() if (endpoints) { ObjectLock olock(endpoints); - for (const String& endpoint : endpoints) { + for (String endpoint : endpoints) { Endpoint::Ptr ep = Endpoint::GetByName(endpoint); if (ep) @@ -60,7 +60,7 @@ std::set Zone::GetEndpoints() const if (endpoints) { ObjectLock olock(endpoints); - for (const String& name : endpoints) { + for (String name : endpoints) { Endpoint::Ptr endpoint = Endpoint::GetByName(name); if (!endpoint) diff --git a/plugins/check_nscp_api.cpp b/plugins/check_nscp_api.cpp index aef43fb98..1a4f5571e 100644 --- a/plugins/check_nscp_api.cpp +++ b/plugins/check_nscp_api.cpp @@ -119,7 +119,7 @@ static int FormatOutput(const Dictionary::Ptr& result) ObjectLock olock(perfs); - for (const Dictionary::Ptr& perf : perfs) { + for (Dictionary::Ptr perf : perfs) { ssout << "'" << perf->Get("alias") << "'="; Dictionary::Ptr values = perf->Get("float_value"); @@ -483,7 +483,7 @@ int main(int argc, char **argv) endpoint += '/'; else { endpoint += '?'; - for (const String& argument : vm["arguments"].as>()) { + for (String argument : vm["arguments"].as>()) { String::SizeType pos = argument.FindFirstOf("="); if (pos == String::NPos) endpoint += Utility::EscapeString(argument, ACQUERY_ENCODE, false); diff --git a/test/icinga-legacytimeperiod.cpp b/test/icinga-legacytimeperiod.cpp index 8661b75f7..b6734f5ab 100644 --- a/test/icinga-legacytimeperiod.cpp +++ b/test/icinga-legacytimeperiod.cpp @@ -574,7 +574,7 @@ BOOST_AUTO_TEST_CASE(dst) std::vector tests; // 2021-03-14: 01:59:59 PST (UTC-8) -> 03:00:00 PDT (UTC-7) - for (const std::string& day : {"2021-03-14", "sunday", "sunday 2", "sunday -3"}) { + for (std::string day : {"2021-03-14", "sunday", "sunday 2", "sunday -3"}) { // range before DST change tests.push_back(TestData{ day, "00:30-01:30", @@ -648,7 +648,7 @@ BOOST_AUTO_TEST_CASE(dst) } // 2021-11-07: 01:59:59 PDT (UTC-7) -> 01:00:00 PST (UTC-8) - for (const std::string& day : {"2021-11-07", "sunday", "sunday 1", "sunday -4"}) { + for (std::string day : {"2021-11-07", "sunday", "sunday 1", "sunday -4"}) { // range before DST change tests.push_back(TestData{ day, "00:15-00:45", From 43f78a4b86d2e1be3ca6ca43c0106518b72c008c Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Wed, 23 Apr 2025 09:13:04 +0200 Subject: [PATCH 260/415] Fix SIGABRT not causing a core dump A second abort() is needed at the end of `SigAbrtHandler()` to trigger the SIG_DFL action (in this case the core dump). Also since `AttachDebugger()` disables the ability to dump core, so it gets reenabled after returning from it. --- lib/base/application.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/base/application.cpp b/lib/base/application.cpp index 89a0f55a2..e450b3300 100644 --- a/lib/base/application.cpp +++ b/lib/base/application.cpp @@ -776,6 +776,12 @@ void Application::SigAbrtHandler(int) } AttachDebugger(fname, interactive_debugger); + +#ifdef __linux__ + prctl(PR_SET_DUMPABLE, 1); +#endif /* __linux __ */ + + abort(); } #ifdef _WIN32 From f4923370ad4fc8ff7d14eb98391b68fd9ac69a75 Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Thu, 17 Apr 2025 11:14:09 +0200 Subject: [PATCH 261/415] Update AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 4207f57f8..dcd471c6b 100644 --- a/AUTHORS +++ b/AUTHORS @@ -135,6 +135,7 @@ Jesse Morgan Jo Goossens Jochen Friedrich Johannes Meyer +Johannes Schmidt Jonas Meurer Jordi van Scheijen Josef Friedrich From 1da497be89db7d8603ff8da3543200eda4092dcd Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 23 Apr 2025 12:06:26 +0200 Subject: [PATCH 262/415] Fix inverted `IsHACluster` check --- lib/remote/apilistener.cpp | 6 +----- lib/remote/zone.cpp | 6 +++--- lib/remote/zone.hpp | 2 +- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 6bcf5bb5f..db024c987 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -1927,11 +1927,7 @@ void ApiListener::ValidateTlsHandshakeTimeout(const Lazy& lvalue, const bool ApiListener::IsHACluster() { Zone::Ptr zone = Zone::GetLocalZone(); - - if (!zone) - return false; - - return zone->IsSingleInstance(); + return zone && zone->IsHACluster(); } /* Provide a helper function for zone origin name. */ diff --git a/lib/remote/zone.cpp b/lib/remote/zone.cpp index 5ae1468c1..16e4ba367 100644 --- a/lib/remote/zone.cpp +++ b/lib/remote/zone.cpp @@ -125,10 +125,10 @@ bool Zone::IsGlobal() const return GetGlobal(); } -bool Zone::IsSingleInstance() const +bool Zone::IsHACluster() const { - Array::Ptr endpoints = GetEndpointsRaw(); - return !endpoints || endpoints->GetLength() < 2; + auto endpoints = GetEndpointsRaw(); + return endpoints && endpoints->GetLength() >= 2; } Zone::Ptr Zone::GetLocalZone() diff --git a/lib/remote/zone.hpp b/lib/remote/zone.hpp index 897b18e96..26b50dcf9 100644 --- a/lib/remote/zone.hpp +++ b/lib/remote/zone.hpp @@ -29,7 +29,7 @@ public: bool CanAccessObject(const ConfigObject::Ptr& object); bool IsChildOf(const Zone::Ptr& zone); bool IsGlobal() const; - bool IsSingleInstance() const; + bool IsHACluster() const; static Zone::Ptr GetLocalZone(); From 353386f404b7a68d91cbed33b2f2e7efe2e20452 Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Tue, 22 Apr 2025 09:18:05 +0200 Subject: [PATCH 263/415] Abort verified JSON-RPC connections with no valid endpoint --- lib/remote/apilistener.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 6bcf5bb5f..2b44c5183 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -834,6 +834,12 @@ void ApiListener::NewClientHandlerInternal( if (ctype == ClientJsonRpc) { Log(LogNotice, "ApiListener", "New JSON-RPC client"); + if (verify_ok && !endpoint) { + Log(LogWarning, "ApiListener") + << "Unknown endpoint '" << identity << "' with valid certificate. Aborting JSON-RPC connection."; + return; + } + if (endpoint && endpoint->GetConnected()) { Log(LogInformation, "ApiListener") << "Ignoring JSON-RPC connection " << conninfo From efc5de3b00569ec77db231a92581bd841104b614 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Fri, 25 Apr 2025 09:57:46 +0200 Subject: [PATCH 264/415] doc/08-advanced-topics.md: Reference CheckResult state Reference the Check Result State Mapping table for the CheckResult state field. This table covers both Service and Host states while the prior documentation string only covered Services. This change is useful since there are different kinds of states for Hosts when using the Icinga 2 API. For one, there is a "normalized" version of 0 for UP and 1 for DOWN. Then there is the exit code version for 0/1 for UP and 2/3 for DOWN. Unfortunately, often this depends on the context and sometimes even intermingles. To make it obvious which kind of state one can expect for a CheckResult object, I have linked to the already existing documentation section. --- doc/08-advanced-topics.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/08-advanced-topics.md b/doc/08-advanced-topics.md index 4f468da61..df48d6bdc 100644 --- a/doc/08-advanced-topics.md +++ b/doc/08-advanced-topics.md @@ -1181,7 +1181,7 @@ to represent its internal state. The following types are exposed via the [API](1 performance\_data | Array | Array of [performance data values](08-advanced-topics.md#advanced-value-types-perfdatavalue). check\_source | String | Name of the node executing the check. scheduling\_source | String | Name of the node scheduling the check. - state | Number | The current state (0 = OK, 1 = WARNING, 2 = CRITICAL, 3 = UNKNOWN). + state | Number | Current state according to the [check result state mapping](03-monitoring-basics.md#check-result-state-mapping). command | Value | Array of command with shell-escaped arguments or command line string. execution\_start | Timestamp | Check execution start time (as a UNIX timestamp). execution\_end | Timestamp | Check execution end time (as a UNIX timestamp). From 595d11de82ec8b89385717e9abf470cc9b3d2dcf Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Tue, 22 Apr 2025 11:58:56 +0200 Subject: [PATCH 265/415] Fix some minor typos and capitalization errors in documentation Corrected one instance where `CMake` was spelled as `cmake` despite the rest of the docs using the first spelling. --- doc/19-technical-concepts.md | 2 +- doc/21-development.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/19-technical-concepts.md b/doc/19-technical-concepts.md index de68bdd63..2d25602d8 100644 --- a/doc/19-technical-concepts.md +++ b/doc/19-technical-concepts.md @@ -204,7 +204,7 @@ You can read the full story [here](https://github.com/Icinga/icinga2/issues/7309 With 2.11 you'll now see 3 processes: -- The umbrella process which takes care about signal handling and process spawning/stopping +- The umbrella process which takes care of signal handling and process spawning/stopping - The main process with the check scheduler, notifications, etc. - The execution helper process diff --git a/doc/21-development.md b/doc/21-development.md index cceaece20..2a3c23d13 100644 --- a/doc/21-development.md +++ b/doc/21-development.md @@ -741,7 +741,7 @@ perfdata | Performance data related, including Graphite, Elastic, etc. db\_ido | IDO database abstraction layer. db\_ido\_mysql | IDO database driver for MySQL. db\_ido\_pgsql | IDO database driver for PgSQL. -mysql\_shin | Library stub for linking against the MySQL client libraries. +mysql\_shim | Library stub for linking against the MySQL client libraries. pgsql\_shim | Library stub for linking against the PgSQL client libraries. #### Class Compiler @@ -1228,7 +1228,7 @@ every second. Avoid log messages which could irritate the user. During implementation, developers can change log levels to better -see what's going one, but remember to change this back to `debug` +see what's going on, but remember to change this back to `debug` or remove it entirely. @@ -2262,7 +2262,7 @@ cmake .. -DCMAKE_INSTALL_PREFIX=/tmp/icinga2 ### CMake Variables -In addition to `CMAKE_INSTALL_PREFIX` here are most of the supported Icinga-specific cmake variables. +In addition to `CMAKE_INSTALL_PREFIX` here are most of the supported Icinga-specific CMake variables. For all variables regarding defaults paths on in CMake, see [GNUInstallDirs](https://cmake.org/cmake/help/latest/module/GNUInstallDirs.html). From 28863a0e41e660dbcbf3b244382e12c276ff89ff Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Tue, 22 Apr 2025 12:01:42 +0200 Subject: [PATCH 266/415] Remove Atom and add Emacs as a "preferred editor" Atom is already dead. But Emacs will outlast human civilization. --- doc/21-development.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/21-development.md b/doc/21-development.md index 2a3c23d13..32d594f6d 100644 --- a/doc/21-development.md +++ b/doc/21-development.md @@ -683,7 +683,7 @@ these tools: - vim - CLion (macOS, Linux) - MS Visual Studio (Windows) -- Atom +- Emacs Editors differ on the functionality. The more helpers you get for C++ development, the faster your development workflow will be. From 8ac71531207111f590b1db3924b11ec2f0c4247a Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Tue, 22 Apr 2025 12:14:48 +0200 Subject: [PATCH 267/415] Rephrasing a sentence about recognizing .ti files You can only 'recognize' something you already know and 'another thing', while not necessarily wrong does not seem right here when there isn't a 'first thing' to notice. --- doc/21-development.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/21-development.md b/doc/21-development.md index 32d594f6d..8c57e7074 100644 --- a/doc/21-development.md +++ b/doc/21-development.md @@ -746,7 +746,7 @@ pgsql\_shim | Library stub for linking against the PgSQL client libraries. #### Class Compiler -Another thing you will recognize are the `.ti` files which are compiled +Something else you might notice are the `.ti` files which are compiled by our own class compiler into actual source code. The meta language allows developers to easily add object attributes and specify their behaviour. From 064399e6f1127bac01b663fe165f3bcd94df3cd7 Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Tue, 22 Apr 2025 12:18:34 +0200 Subject: [PATCH 268/415] Rephrase some sentences about unity builds in dev docs We didn't really 'invent' unity builds, more like adopted them, since this is a common practice. Also added a reference to the actual CMake variable in question instead of just alluding to it --- doc/21-development.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/doc/21-development.md b/doc/21-development.md index 8c57e7074..bf458cfb2 100644 --- a/doc/21-development.md +++ b/doc/21-development.md @@ -792,17 +792,18 @@ The most common benefits: #### Unity Builds -Another thing you should be aware of: Unity builds on and off. +You should be aware that by default unity builds are enabled. You can turn them +off by setting the `ICINGA2_UNITY_BUILD` CMake option to `OFF`. Typically, we already use caching mechanisms to reduce recompile time with ccache. For release builds, there's always a new build needed as the difference is huge compared to a previous (major) release. -Therefore we've invented the Unity builds, which basically concatenates all source files -into one big library source code file. The compiler then doesn't need to load the many small -files but compiles and links this huge one. +Unity builds basically concatenate all source files into one big library source code file. +The compiler then doesn't need to load many small files, each with all of their includes, +but compiles and links only a few huge ones. -Unity builds require more memory which is why you should disable them for development +However, unity builds require more memory which is why you should disable them for development builds in small sized VMs (Linux, Windows) and also Docker containers. There's a couple of header files which are included everywhere. If you touch/edit them, From de4b58a04fdc53a1a678c49d819f75e24ffe5fff Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Fri, 25 Apr 2025 16:09:54 +0200 Subject: [PATCH 269/415] tests: Move make_tm helper function to utils file Preparation to be able to use the function from different test files later on. --- test/CMakeLists.txt | 1 + test/icinga-legacytimeperiod.cpp | 30 +------------------------- test/utils.cpp | 36 ++++++++++++++++++++++++++++++++ test/utils.hpp | 8 +++++++ 4 files changed, 46 insertions(+), 29 deletions(-) create mode 100644 test/utils.cpp create mode 100644 test/utils.hpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 810b35bef..8ed300c1f 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -57,6 +57,7 @@ add_boost_test(types set(base_test_SOURCES icingaapplication-fixture.cpp + utils.cpp base-array.cpp base-base64.cpp base-convert.cpp diff --git a/test/icinga-legacytimeperiod.cpp b/test/icinga-legacytimeperiod.cpp index 8661b75f7..d279a1cbb 100644 --- a/test/icinga-legacytimeperiod.cpp +++ b/test/icinga-legacytimeperiod.cpp @@ -2,6 +2,7 @@ #include "base/utility.hpp" #include "icinga/legacytimeperiod.hpp" +#include "test/utils.hpp" #include #include #include @@ -471,35 +472,6 @@ BOOST_AUTO_TEST_CASE(advanced) AdvancedHelper("09:00:03-30:00:04", {{2014, 9, 24}, {9, 0, 3}}, {{2014, 9, 25}, {6, 0, 4}}); } -tm make_tm(std::string s) -{ - int dst = -1; - size_t l = strlen("YYYY-MM-DD HH:MM:SS"); - if (s.size() > l) { - std::string zone = s.substr(l); - if (zone == " PST") { - dst = 0; - } else if (zone == " PDT") { - dst = 1; - } else { - // tests should only use PST/PDT (for now) - BOOST_CHECK_MESSAGE(false, "invalid or unknown time time: " << zone); - } - } - - std::tm t = {}; -#if defined(__GNUC__) && __GNUC__ < 5 - // GCC did not implement std::get_time() until version 5 - strptime(s.c_str(), "%Y-%m-%d %H:%M:%S", &t); -#else /* defined(__GNUC__) && __GNUC__ < 5 */ - std::istringstream stream(s); - stream >> std::get_time(&t, "%Y-%m-%d %H:%M:%S"); -#endif /* defined(__GNUC__) && __GNUC__ < 5 */ - t.tm_isdst = dst; - - return t; -} - time_t make_time_t(const tm* t) { tm copy = *t; diff --git a/test/utils.cpp b/test/utils.cpp new file mode 100644 index 000000000..4008db4a1 --- /dev/null +++ b/test/utils.cpp @@ -0,0 +1,36 @@ +/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */ + +#include "utils.hpp" +#include +#include +#include +#include + +tm make_tm(std::string s) +{ + int dst = -1; + size_t l = strlen("YYYY-MM-DD HH:MM:SS"); + if (s.size() > l) { + std::string zone = s.substr(l); + if (zone == " PST") { + dst = 0; + } else if (zone == " PDT") { + dst = 1; + } else { + // tests should only use PST/PDT (for now) + BOOST_CHECK_MESSAGE(false, "invalid or unknown time time: " << zone); + } + } + + std::tm t = {}; +#if defined(__GNUC__) && __GNUC__ < 5 + // GCC did not implement std::get_time() until version 5 + strptime(s.c_str(), "%Y-%m-%d %H:%M:%S", &t); +#else /* defined(__GNUC__) && __GNUC__ < 5 */ + std::istringstream stream(s); + stream >> std::get_time(&t, "%Y-%m-%d %H:%M:%S"); +#endif /* defined(__GNUC__) && __GNUC__ < 5 */ + t.tm_isdst = dst; + + return t; +} diff --git a/test/utils.hpp b/test/utils.hpp new file mode 100644 index 000000000..213c10e4b --- /dev/null +++ b/test/utils.hpp @@ -0,0 +1,8 @@ +/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */ + +#pragma once + +#include +#include + +tm make_tm(std::string s); From 2458f686db271b6307cec98fd71573015c0e63ac Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Fri, 25 Apr 2025 16:17:28 +0200 Subject: [PATCH 270/415] tests: Remove GCC compatibility from make_tm We're using C++17, GCC only started implementing that in version 5, so there's no need for compatibility code for older versions any more. --- test/utils.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/test/utils.cpp b/test/utils.cpp index 4008db4a1..41b6a1067 100644 --- a/test/utils.cpp +++ b/test/utils.cpp @@ -23,13 +23,8 @@ tm make_tm(std::string s) } std::tm t = {}; -#if defined(__GNUC__) && __GNUC__ < 5 - // GCC did not implement std::get_time() until version 5 - strptime(s.c_str(), "%Y-%m-%d %H:%M:%S", &t); -#else /* defined(__GNUC__) && __GNUC__ < 5 */ std::istringstream stream(s); stream >> std::get_time(&t, "%Y-%m-%d %H:%M:%S"); -#endif /* defined(__GNUC__) && __GNUC__ < 5 */ t.tm_isdst = dst; return t; From 17c96783cf433e1a49ded86a1a19850b15889bf5 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Thu, 24 Apr 2025 17:05:31 +0200 Subject: [PATCH 271/415] tests: Move GlobalTimezoneFixture to utils file Prepare for adding test cases for DST changes in other files as well. --- test/icinga-legacytimeperiod.cpp | 48 ++------------------------------ test/utils.cpp | 36 ++++++++++++++++++++++++ test/utils.hpp | 17 +++++++++++ 3 files changed, 55 insertions(+), 46 deletions(-) diff --git a/test/icinga-legacytimeperiod.cpp b/test/icinga-legacytimeperiod.cpp index d279a1cbb..cecef81e5 100644 --- a/test/icinga-legacytimeperiod.cpp +++ b/test/icinga-legacytimeperiod.cpp @@ -16,52 +16,8 @@ using namespace icinga; BOOST_AUTO_TEST_SUITE(icinga_legacytimeperiod); -struct GlobalTimezoneFixture -{ - char *tz; - - GlobalTimezoneFixture(const char *fixed_tz = "") - { - tz = getenv("TZ"); -#ifdef _WIN32 - _putenv_s("TZ", fixed_tz == "" ? "UTC" : fixed_tz); -#else - setenv("TZ", fixed_tz, 1); -#endif - tzset(); - } - - ~GlobalTimezoneFixture() - { -#ifdef _WIN32 - if (tz) - _putenv_s("TZ", tz); - else - _putenv_s("TZ", ""); -#else - if (tz) - setenv("TZ", tz, 1); - else - unsetenv("TZ"); -#endif - tzset(); - } -}; - BOOST_GLOBAL_FIXTURE(GlobalTimezoneFixture); -// DST changes in America/Los_Angeles: -// 2021-03-14: 01:59:59 PST (UTC-8) -> 03:00:00 PDT (UTC-7) -// 2021-11-07: 01:59:59 PDT (UTC-7) -> 01:00:00 PST (UTC-8) -#ifndef _WIN32 -static const char *dst_test_timezone = "America/Los_Angeles"; -#else /* _WIN32 */ -// Tests are using pacific time because Windows only really supports timezones following US DST rules with the TZ -// environment variable. Format is "[Standard TZ][negative UTC offset][DST TZ]". -// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/tzset?view=msvc-160#remarks -static const char *dst_test_timezone = "PST8PDT"; -#endif /* _WIN32 */ - BOOST_AUTO_TEST_CASE(simple) { tm tm_beg, tm_end, tm_ref; @@ -523,7 +479,7 @@ std::ostream& operator<<(std::ostream& o, const boost::optional& s) BOOST_AUTO_TEST_CASE(dst) { - GlobalTimezoneFixture tz(dst_test_timezone); + GlobalTimezoneFixture tz(GlobalTimezoneFixture::TestTimezoneWithDST); // Self-tests for helper functions BOOST_CHECK_EQUAL(make_tm("2021-11-07 02:30:00").tm_isdst, -1); @@ -802,7 +758,7 @@ BOOST_AUTO_TEST_CASE(dst) // This tests checks that TimePeriod::IsInside() always returns true for a 24x7 period, even around DST changes. BOOST_AUTO_TEST_CASE(dst_isinside) { - GlobalTimezoneFixture tz(dst_test_timezone); + GlobalTimezoneFixture tz(GlobalTimezoneFixture::TestTimezoneWithDST); Function::Ptr update = new Function("LegacyTimePeriod", LegacyTimePeriod::ScriptFunc, {"tp", "begin", "end"}); Dictionary::Ptr ranges = new Dictionary({ diff --git a/test/utils.cpp b/test/utils.cpp index 41b6a1067..316763670 100644 --- a/test/utils.cpp +++ b/test/utils.cpp @@ -29,3 +29,39 @@ tm make_tm(std::string s) return t; } + +#ifndef _WIN32 +const char *GlobalTimezoneFixture::TestTimezoneWithDST = "America/Los_Angeles"; +#else /* _WIN32 */ +// Tests are using pacific time because Windows only really supports timezones following US DST rules with the TZ +// environment variable. Format is "[Standard TZ][negative UTC offset][DST TZ]". +// https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/tzset?view=msvc-160#remarks +const char *GlobalTimezoneFixture::TestTimezoneWithDST = "PST8PDT"; +#endif /* _WIN32 */ + +GlobalTimezoneFixture::GlobalTimezoneFixture(const char *fixed_tz) +{ + tz = getenv("TZ"); +#ifdef _WIN32 + _putenv_s("TZ", fixed_tz == "" ? "UTC" : fixed_tz); +#else + setenv("TZ", fixed_tz, 1); +#endif + tzset(); +} + +GlobalTimezoneFixture::~GlobalTimezoneFixture() +{ +#ifdef _WIN32 + if (tz) + _putenv_s("TZ", tz); + else + _putenv_s("TZ", ""); +#else + if (tz) + setenv("TZ", tz, 1); + else + unsetenv("TZ"); +#endif + tzset(); +} diff --git a/test/utils.hpp b/test/utils.hpp index 213c10e4b..af44d3b88 100644 --- a/test/utils.hpp +++ b/test/utils.hpp @@ -6,3 +6,20 @@ #include tm make_tm(std::string s); + +struct GlobalTimezoneFixture +{ + /** + * Timezone used for testing DST changes. + * + * DST changes in America/Los_Angeles: + * 2021-03-14: 01:59:59 PST (UTC-8) -> 03:00:00 PDT (UTC-7) + * 2021-11-07: 01:59:59 PDT (UTC-7) -> 01:00:00 PST (UTC-8) + */ + static const char *TestTimezoneWithDST; + + GlobalTimezoneFixture(const char *fixed_tz = ""); + ~GlobalTimezoneFixture(); + + char *tz; +}; From 5404143dee89e7e4f338dfcfffca2446b249f633 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Thu, 24 Apr 2025 17:15:51 +0200 Subject: [PATCH 272/415] Ensure consistent mktime() DST behavior across different implementations There are inputs to mktime() where the behavior is not specified and there's also no single obviously correct behavior. In particular, this affects how auto-detection of whether DST is in effect is done when tm_isdst = -1 is set and the time specified does not exist at all or exists twice on that day. If different implementations are used within an Icinga 2 cluster, that can lead to inconsistent behavior because different nodes may interpret the same TimePeriod differently. This commit introduces a wrapper to mktime(), namely Utility::NormalizeTm() that implements the behavior provided by glibc. The choice for glibc's behavior is pretty arbitrary, it was simply picked because most systems that are officially/fully supported use it (with the only exception being Windows), so this should give the least possible amount of user-visible changes. As part of this commit, the closely related helper function mktime_const() is also moved to Utility::TmToTimestamp() and made a wrapper around the newly introduced NormalizeTm(). --- lib/base/datetime.cpp | 2 +- lib/base/utility.cpp | 48 +++++++++++++++++++ lib/base/utility.hpp | 3 ++ lib/icinga/legacytimeperiod.cpp | 45 +++++++----------- test/CMakeLists.txt | 1 + test/base-utility.cpp | 83 +++++++++++++++++++++++++++++++++ 6 files changed, 153 insertions(+), 29 deletions(-) diff --git a/lib/base/datetime.cpp b/lib/base/datetime.cpp index aa7b5e59e..3e443e613 100644 --- a/lib/base/datetime.cpp +++ b/lib/base/datetime.cpp @@ -35,7 +35,7 @@ DateTime::DateTime(const std::vector& args) tms.tm_isdst = -1; - m_Value = mktime(&tms); + m_Value = Utility::TmToTimestamp(&tms); } else if (args.size() == 1) m_Value = args[0]; else diff --git a/lib/base/utility.cpp b/lib/base/utility.cpp index a2e461e5c..2b4e32def 100644 --- a/lib/base/utility.cpp +++ b/lib/base/utility.cpp @@ -1958,3 +1958,51 @@ bool Utility::ComparePasswords(const String& enteredPassword, const String& actu return result; } + +/** + * Normalizes the given struct tm like mktime() from libc does with some exception for DST handling: If the given time + * exists twice on a day, the instance in the DST timezone is picked. If the time does not actually exist on a day, it's + * interpreted using the UTC offset of the standard timezone and then normalized. + * + * This is done in order to provide consistent behavior across operating systems. Historically, Icinga 2 just relied on + * whatever mktime() of the operating system did and this function mimics what glibc does as that's what most systems + * use. + * + * @param t tm struct to be normalized + * @return time_t representing the timestamp given by t + */ +time_t Utility::NormalizeTm(tm *t) +{ + // If tm_isdst already specifies the timezone (0 or 1), just use the mktime() behavior. + if (t->tm_isdst >= 0) { + return mktime(t); + } + + const tm copy = *t; + + t->tm_isdst = 1; + time_t result = mktime(t); + if (result != -1 && t->tm_isdst == 1) { + return result; + } + + // Restore the original input. mktime() can (and does) change more fields than just tm_isdst by converting from + // daylight saving time to standard time (it moves the contents by (typically) an hour, which can move across + // days/weeks/months/years changing all other fields). + *t = copy; + + t->tm_isdst = 0; + return mktime(t); +} + +/** + * Returns the same as NormalizeTm() but takes a const pointer as argument and thus does not modify it. + * + * @param t struct tm to convert to time_t + * @return time_t representing the timestamp given by t + */ +time_t Utility::TmToTimestamp(const tm *t) +{ + tm copy = *t; + return NormalizeTm(©); +} diff --git a/lib/base/utility.hpp b/lib/base/utility.hpp index 5132673ca..2a2fe0c1b 100644 --- a/lib/base/utility.hpp +++ b/lib/base/utility.hpp @@ -185,6 +185,9 @@ public: return in.SubStr(0, maxLength - sha1HexLength - strlen(trunc)) + trunc + SHA1(in); } + static time_t NormalizeTm(tm *t); + static time_t TmToTimestamp(const tm *t); + private: Utility(); diff --git a/lib/icinga/legacytimeperiod.cpp b/lib/icinga/legacytimeperiod.cpp index 55bc58ed3..6d2433758 100644 --- a/lib/icinga/legacytimeperiod.cpp +++ b/lib/icinga/legacytimeperiod.cpp @@ -13,23 +13,12 @@ using namespace icinga; REGISTER_FUNCTION_NONCONST(Internal, LegacyTimePeriod, &LegacyTimePeriod::ScriptFunc, "tp:begin:end"); -/** - * Returns the same as mktime() but does not modify its argument and takes a const pointer. - * - * @param t struct tm to convert to time_t - * @return time_t representing the timestamp given by t - */ -static time_t mktime_const(const tm *t) { - tm copy = *t; - return mktime(©); -} - bool LegacyTimePeriod::IsInTimeRange(const tm *begin, const tm *end, int stride, const tm *reference) { time_t tsbegin, tsend, tsref; - tsbegin = mktime_const(begin); - tsend = mktime_const(end); - tsref = mktime_const(reference); + tsbegin = Utility::TmToTimestamp(begin); + tsend = Utility::TmToTimestamp(end); + tsref = Utility::TmToTimestamp(reference); if (tsref < tsbegin || tsref >= tsend) return false; @@ -85,7 +74,7 @@ void LegacyTimePeriod::FindNthWeekday(int wday, int n, tm *reference) t.tm_sec = 0; t.tm_isdst = -1; - mktime(&t); + Utility::NormalizeTm(&t); if (t.tm_wday == wday) { seen++; @@ -398,8 +387,8 @@ bool LegacyTimePeriod::IsInDayDefinition(const String& daydef, const tm *referen ParseTimeRange(daydef, &begin, &end, &stride, reference); Log(LogDebug, "LegacyTimePeriod") - << "ParseTimeRange: '" << daydef << "' => " << mktime(&begin) - << " -> " << mktime(&end) << ", stride: " << stride; + << "ParseTimeRange: '" << daydef << "' => " << Utility::TmToTimestamp(&begin) + << " -> " << Utility::TmToTimestamp(&end) << ", stride: " << stride; return IsInTimeRange(&begin, &end, stride, reference); } @@ -448,8 +437,8 @@ Dictionary::Ptr LegacyTimePeriod::ProcessTimeRange(const String& timestamp, cons ProcessTimeRangeRaw(timestamp, reference, &begin, &end); return new Dictionary({ - { "begin", (long)mktime(&begin) }, - { "end", (long)mktime(&end) } + { "begin", (long)Utility::TmToTimestamp(&begin) }, + { "end", (long)Utility::TmToTimestamp(&end) } }); } @@ -480,13 +469,13 @@ Dictionary::Ptr LegacyTimePeriod::FindRunningSegment(const String& daydef, const time_t tsend, tsiter, tsref; int stride; - tsref = mktime_const(reference); + tsref = Utility::TmToTimestamp(reference); ParseTimeRange(daydef, &begin, &end, &stride, reference); iter = begin; - tsend = mktime(&end); + tsend = Utility::NormalizeTm(&end); do { if (IsInTimeRange(&begin, &end, stride, &iter)) { @@ -518,7 +507,7 @@ Dictionary::Ptr LegacyTimePeriod::FindRunningSegment(const String& daydef, const iter.tm_hour = 0; iter.tm_min = 0; iter.tm_sec = 0; - tsiter = mktime(&iter); + tsiter = Utility::NormalizeTm(&iter); } while (tsiter < tsend); return nullptr; @@ -538,13 +527,13 @@ Dictionary::Ptr LegacyTimePeriod::FindNextSegment(const String& daydef, const St ref.tm_mday++; } - tsref = mktime(&ref); + tsref = Utility::NormalizeTm(&ref); ParseTimeRange(daydef, &begin, &end, &stride, &ref); iter = begin; - tsend = mktime(&end); + tsend = Utility::NormalizeTm(&end); do { if (IsInTimeRange(&begin, &end, stride, &iter)) { @@ -575,7 +564,7 @@ Dictionary::Ptr LegacyTimePeriod::FindNextSegment(const String& daydef, const St iter.tm_hour = 0; iter.tm_min = 0; iter.tm_sec = 0; - tsiter = mktime(&iter); + tsiter = Utility::NormalizeTm(&iter); } while (tsiter < tsend); } @@ -607,17 +596,17 @@ Array::Ptr LegacyTimePeriod::ScriptFunc(const TimePeriod::Ptr& tp, double begin, t->tm_isdst = -1; // Normalize fields using mktime. - mktime(t); + Utility::NormalizeTm(t); // Reset tm_isdst so that future calls figure out the correct time zone after setting tm_hour/tm_min/tm_sec. t->tm_isdst = -1; }; - for (tm reference = tm_begin; mktime_const(&reference) <= end; advance_to_next_day(&reference)) { + for (tm reference = tm_begin; Utility::TmToTimestamp(&reference) <= end; advance_to_next_day(&reference)) { #ifdef I2_DEBUG Log(LogDebug, "LegacyTimePeriod") - << "Checking reference time " << mktime_const(&reference); + << "Checking reference time " << Utility::TmToTimestamp(&reference); #endif /* I2_DEBUG */ ObjectLock olock(ranges); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8ed300c1f..0e2a2976e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -186,6 +186,7 @@ add_boost_test(base base_utility/EscapeCreateProcessArg base_utility/TruncateUsingHash base_utility/FormatDateTime + base_utility/NormalizeTm base_value/scalar base_value/convert base_value/format diff --git a/test/base-utility.cpp b/test/base-utility.cpp index 2273e1b8c..0e6e0c645 100644 --- a/test/base-utility.cpp +++ b/test/base-utility.cpp @@ -1,6 +1,7 @@ /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ #include "base/utility.hpp" +#include "test/utils.hpp" #include #include @@ -230,4 +231,86 @@ BOOST_AUTO_TEST_CASE(FormatDateTime) { BOOST_CHECK_THROW(Utility::FormatDateTime("%Y", positive_out_of_range), positive_overflow); } +BOOST_AUTO_TEST_CASE(NormalizeTm) +{ + GlobalTimezoneFixture tz(GlobalTimezoneFixture::TestTimezoneWithDST); + + auto normalize = [](const std::string_view& input) { + tm t = make_tm(std::string(input)); + return Utility::NormalizeTm(&t); + }; + + auto is_dst = [](const std::string_view& input) { + tm t = make_tm(std::string(input)); + Utility::NormalizeTm(&t); + BOOST_CHECK_GE(t.tm_isdst, 0); + return t.tm_isdst > 0; + }; + + // The whole day 2021-01-01 uses PST (24h day) + BOOST_CHECK(!is_dst("2021-01-01 10:00:00")); + BOOST_CHECK_EQUAL(normalize("2021-01-01 10:00:00"), 1609524000); + BOOST_CHECK_EQUAL(normalize("2021-01-01 10:00:00 PST"), 1609524000); + BOOST_CHECK_EQUAL(normalize("2021-01-01 11:00:00 PDT"), 1609524000); // normalized to 10:00 PST + BOOST_CHECK_EQUAL(normalize("2021-01-02 00:00:00") - normalize("2021-01-01 00:00:00"), 24*60*60); + + // The whole day 2021-07-01 uses PDT (24h day) + BOOST_CHECK(is_dst("2021-07-01 10:00:00")); + BOOST_CHECK_EQUAL(normalize("2021-07-01 10:00:00"), 1625158800); + BOOST_CHECK_EQUAL(normalize("2021-07-01 10:00:00 PDT"), 1625158800); + BOOST_CHECK_EQUAL(normalize("2021-07-01 09:00:00 PST"), 1625158800); // normalized to 10:00 PDT + BOOST_CHECK_EQUAL(normalize("2021-07-02 00:00:00") - normalize("2021-07-01 00:00:00"), 24*60*60); + + // On 2021-03-14, PST changes to PDT (23h day) + BOOST_CHECK(!is_dst("2021-03-14 00:00:00")); + BOOST_CHECK(is_dst("2021-03-14 23:59:59")); + BOOST_CHECK_EQUAL(normalize("2021-03-15 00:00:00") - normalize("2021-03-14 00:00:00"), 23*60*60); + + BOOST_CHECK_EQUAL(normalize("2021-03-14 01:59:59 PST"), 1615715999); + // The following three times do not exist on that day in that timezone. + // They are interpreted as UTC-8, which is the offset of PST. + BOOST_CHECK_EQUAL(normalize("2021-03-14 02:00:00 PST"), 1615716000); + BOOST_CHECK_EQUAL(normalize("2021-03-14 02:30:00 PST"), 1615717800); + BOOST_CHECK_EQUAL(normalize("2021-03-14 03:00:00 PST"), 1615719600); + + BOOST_CHECK_EQUAL(normalize("2021-03-14 03:00:00 PDT"), 1615716000); + // The following three times do not exist on that day in that timezone. + // They are interpreted as UTC-7, which is the offset of PDT. + BOOST_CHECK_EQUAL(normalize("2021-03-14 01:59:59 PDT"), 1615712399); + BOOST_CHECK_EQUAL(normalize("2021-03-14 02:00:00 PDT"), 1615712400); + BOOST_CHECK_EQUAL(normalize("2021-03-14 02:30:00 PDT"), 1615714200); + + BOOST_CHECK_EQUAL(normalize("2021-03-14 01:59:59"), 1615715999); + BOOST_CHECK_EQUAL(normalize("2021-03-14 03:00:00"), 1615716000); + // The following two times don't exist on that day, they are within the hour that is skipped. + // They are interpreted as UTC-8 (offset of PST) and then normalized to PDT. + BOOST_CHECK_EQUAL(normalize("2021-03-14 02:00:00"), 1615716000); + BOOST_CHECK_EQUAL(normalize("2021-03-14 02:30:00"), 1615717800); + + // On 2021-11-07, PDT changes to PST (25h day) + BOOST_CHECK(is_dst("2021-11-07 00:00:00")); + BOOST_CHECK(!is_dst("2021-11-07 23:59:59")); + BOOST_CHECK_EQUAL(normalize("2021-11-08 00:00:00") - normalize("2021-11-07 00:00:00"), 25*60*60); + + BOOST_CHECK_EQUAL(normalize("2021-11-07 00:59:59 PDT"), 1636271999); + BOOST_CHECK_EQUAL(normalize("2021-11-07 01:00:00 PDT"), 1636272000); + BOOST_CHECK_EQUAL(normalize("2021-11-07 01:30:00 PDT"), 1636273800); + BOOST_CHECK_EQUAL(normalize("2021-11-07 01:59:59 PDT"), 1636275599); + // The following time does not exist on that day in that timezone, it's interpreted as 01:00:00 PST. + BOOST_CHECK_EQUAL(normalize("2021-11-07 02:00:00 PDT"), 1636275600); + + // The following time does not exist on that day in that timezone, it's interpreted as 01:59:59 PDT. + BOOST_CHECK_EQUAL(normalize("2021-11-07 00:59:59 PST"), 1636275599); + BOOST_CHECK_EQUAL(normalize("2021-11-07 01:00:00 PST"), 1636275600); + BOOST_CHECK_EQUAL(normalize("2021-11-07 01:30:00 PST"), 1636277400); + BOOST_CHECK_EQUAL(normalize("2021-11-07 01:59:59 PST"), 1636279199); + BOOST_CHECK_EQUAL(normalize("2021-11-07 02:00:00 PST"), 1636279200); + + BOOST_CHECK_EQUAL(normalize("2021-11-07 00:59:59"), 1636271999); // unambiguous: PDT + BOOST_CHECK_EQUAL(normalize("2021-11-07 01:00:00"), 1636272000); // exists twice, interpreted as PDT + BOOST_CHECK_EQUAL(normalize("2021-11-07 01:30:00"), 1636273800); // exists twice, interpreted as PDT + BOOST_CHECK_EQUAL(normalize("2021-11-07 01:59:59"), 1636275599); // exists twice, interpreted as PDT + BOOST_CHECK_EQUAL(normalize("2021-11-07 02:00:00"), 1636279200); // unambiguous: PST +} + BOOST_AUTO_TEST_SUITE_END() From 379d7638ed686c39edaeae6844bcb96c1b71e482 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Tue, 22 Apr 2025 17:07:41 +0200 Subject: [PATCH 273/415] tests: Remove special cases for Windows in icinga_legacytimeperiod/dst Ideally, Icinga 2 should behave consistenly across platforms. These special cases only existed because mktime() on Windows behaved differently than the implementation in glibc. With the introduction of Utility::NormalizeTm(), there's now consistent behavior and the other expected results for windows are no longer necessary (ideally, they shouldn't have existed in the first place). --- test/icinga-legacytimeperiod.cpp | 55 -------------------------------- 1 file changed, 55 deletions(-) diff --git a/test/icinga-legacytimeperiod.cpp b/test/icinga-legacytimeperiod.cpp index cecef81e5..4eb5fb92e 100644 --- a/test/icinga-legacytimeperiod.cpp +++ b/test/icinga-legacytimeperiod.cpp @@ -517,13 +517,8 @@ BOOST_AUTO_TEST_CASE(dst) day, "01:30-02:30", {make_tm("2021-03-14 01:00:00 PST")}, {make_tm("2021-03-14 01:59:59 PST")}, -#ifndef _WIN32 // As 02:30 does not exist on this day, it is parsed as if it was 02:30 PST which is actually 03:30 PDT. Segment("2021-03-14 01:30:00 PST", "2021-03-14 03:30:00 PDT"), -#else - // Windows interpretes 02:30 as 01:30 PST, so it is an empty segment. - boost::none, -#endif }); } @@ -533,14 +528,9 @@ BOOST_AUTO_TEST_CASE(dst) day, "02:30-03:30", {make_tm("2021-03-14 01:00:00 PST")}, {make_tm("2021-03-14 03:00:00 PDT")}, -#ifndef _WIN32 // As 02:30 does not exist on this day, it is parsed as if it was 02:30 PST which is actually 03:30 PDT. // Therefore, the result is a segment from 03:30 PDT to 03:30 PDT with a duration of 0, i.e. no segment. boost::none, -#else - // Windows parses non-existing 02:30 as 01:30 PST, resulting in an 1 hour segment. - Segment("2021-03-14 01:30:00 PST", "2021-03-14 03:30:00 PDT"), -#endif }); } @@ -549,13 +539,8 @@ BOOST_AUTO_TEST_CASE(dst) day, "02:15-03:45", {make_tm("2021-03-14 01:00:00 PST")}, {make_tm("2021-03-14 03:30:00 PDT")}, -#ifndef _WIN32 // As 02:15 does not exist on this day, it is parsed as if it was 02:15 PST which is actually 03:15 PDT. Segment("2021-03-14 03:15:00 PDT", "2021-03-14 03:45:00 PDT"), -#else - // Windows interprets 02:15 as 01:15 PST though. - Segment("2021-03-14 01:15:00 PST", "2021-03-14 03:45:00 PDT"), -#endif }); // range after DST change @@ -587,7 +572,6 @@ BOOST_AUTO_TEST_CASE(dst) if (day.find("sunday") == std::string::npos) { // skip for non-absolute day specs (would find another sunday) // range existing twice during DST change (first instance) -#ifndef _WIN32 tests.push_back(TestData{ day, "01:15-01:45", {make_tm("2021-11-07 01:00:00 PDT")}, @@ -595,15 +579,6 @@ BOOST_AUTO_TEST_CASE(dst) // Duplicate times are interpreted as the first occurrence. Segment("2021-11-07 01:15:00 PDT", "2021-11-07 01:45:00 PDT"), }); -#else - tests.push_back(TestData{ - day, "01:15-01:45", - {make_tm("2021-11-07 01:00:00 PDT")}, - {make_tm("2021-11-07 01:30:00 PST")}, - // However, Windows always uses the second occurrence. - Segment("2021-11-07 01:15:00 PST", "2021-11-07 01:45:00 PST"), - }); -#endif } if (day.find("sunday") == std::string::npos) { // skip for non-absolute day specs (would find another sunday) @@ -612,13 +587,8 @@ BOOST_AUTO_TEST_CASE(dst) day, "01:15-01:45", {make_tm("2021-11-07 01:00:00 PST")}, {make_tm("2021-11-07 01:30:00 PST")}, -#ifndef _WIN32 // Interpreted as the first occurrence, so it's in the past. boost::none, -#else - // On Windows, it's the second occurrence, so it's still in the present/future and is found. - Segment("2021-11-07 01:15:00 PST", "2021-11-07 01:45:00 PST"), -#endif }); } @@ -644,13 +614,8 @@ BOOST_AUTO_TEST_CASE(dst) day, "00:30-01:30", {make_tm("2021-11-07 00:00:00 PDT")}, {make_tm("2021-11-07 01:00:00 PDT")}, -#ifndef _WIN32 // Both times are interpreted as the first instance on that day (i.e both PDT). Segment("2021-11-07 00:30:00 PDT", "2021-11-07 01:30:00 PDT") -#else - // Windows interprets duplicate times as the second instance (i.e. both PST). - Segment("2021-11-07 00:30:00 PDT", "2021-11-07 01:30:00 PST") -#endif }); // range beginning during duplicate DST hour (first instance) @@ -658,18 +623,12 @@ BOOST_AUTO_TEST_CASE(dst) day, "01:30-02:30", {make_tm("2021-11-07 01:00:00 PDT")}, {make_tm("2021-11-07 02:00:00 PST")}, -#ifndef _WIN32 // 01:30 is interpreted as the first occurrence (PDT) but since there's no 02:30 PDT, it's PST. Segment("2021-11-07 01:30:00 PDT", "2021-11-07 02:30:00 PST") -#else - // Windows interprets both as PST though. - Segment("2021-11-07 01:30:00 PST", "2021-11-07 02:30:00 PST") -#endif }); if (day.find("sunday") == std::string::npos) { // skip for non-absolute day specs (would find another sunday) // range ending during duplicate DST hour (second instance) -#ifndef _WIN32 tests.push_back(TestData{ day, "00:30-01:30", {make_tm("2021-11-07 00:00:00 PST")}, @@ -678,15 +637,6 @@ BOOST_AUTO_TEST_CASE(dst) // 01:00 PST (02:00 PDT) is after the segment. boost::none, }); -#else - tests.push_back(TestData{ - day, "00:30-01:30", - {make_tm("2021-11-07 00:00:00 PDT")}, - {make_tm("2021-11-07 01:00:00 PST")}, - // As Windows interprets the end as PST, it's still in the future and the segment is found. - Segment("2021-11-07 00:30:00 PDT", "2021-11-07 01:30:00 PST"), - }); -#endif } // range beginning during duplicate DST hour (second instance) @@ -694,13 +644,8 @@ BOOST_AUTO_TEST_CASE(dst) day, "01:30-02:30", {make_tm("2021-11-07 01:00:00 PDT")}, {make_tm("2021-11-07 02:00:00 PST")}, -#ifndef _WIN32 // As 01:30 always refers to the first occurrence (PDT), this is actually a 2 hour segment. Segment("2021-11-07 01:30:00 PDT", "2021-11-07 02:30:00 PST"), -#else - // On Windows, it refers t the second occurrence (PST), therefore it's an 1 hour segment. - Segment("2021-11-07 01:30:00 PST", "2021-11-07 02:30:00 PST"), -#endif }); } From 3c9e06972d094d079fd8856aea43ab78b10d8802 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Tue, 22 Apr 2025 17:28:39 +0200 Subject: [PATCH 274/415] Don't disable icinga_legacytimeperiod/dst test on Alpine The test was disabled due to a difference in behavior of mktime() between glibc and musl. This inconsistency was fixed with the introduction of Utility::NormalizeTm() and Alpine no longer needs this special case. --- .github/workflows/linux.bash | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/linux.bash b/.github/workflows/linux.bash index 6c4240d1a..7eabec9a3 100755 --- a/.github/workflows/linux.bash +++ b/.github/workflows/linux.bash @@ -14,11 +14,6 @@ case "$DISTRO" in # https://gitlab.alpinelinux.org/alpine/aports/-/blob/master/community/icinga2/APKBUILD apk add bison boost-dev ccache cmake flex g++ libedit-dev libressl-dev ninja-build tzdata ln -vs /usr/lib/ninja-build/bin/ninja /usr/local/bin/ninja - - # This test fails due to some glibc/musl mismatch regarding timezone PST/PDT. - # - https://www.openwall.com/lists/musl/2024/03/05/2 - # - https://gitlab.alpinelinux.org/alpine/aports/-/blob/b3ea02e2251451f9511086e1970f21eb640097f7/community/icinga2/disable-failing-tests.patch - sed -i '/icinga_legacytimeperiod\/dst$/d' /icinga2/test/CMakeLists.txt ;; amazonlinux:2) From 331ba1f661bb0e812534bf7c224d6c89dfec3a5c Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 5 Dec 2024 10:54:32 +0100 Subject: [PATCH 275/415] Rename AsioConditionVariable to AsioEvent The current implementation is rather similar to Python's threading.Event, than to a CV. --- lib/base/io-engine.cpp | 8 ++++---- lib/base/io-engine.hpp | 6 +++--- lib/icingadb/redisconnection.hpp | 2 +- lib/remote/jsonrpcconnection.hpp | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/base/io-engine.cpp b/lib/base/io-engine.cpp index 3190ed03d..30e2512d4 100644 --- a/lib/base/io-engine.cpp +++ b/lib/base/io-engine.cpp @@ -124,23 +124,23 @@ void IoEngine::RunEventLoop() } } -AsioConditionVariable::AsioConditionVariable(boost::asio::io_context& io, bool init) +AsioEvent::AsioEvent(boost::asio::io_context& io, bool init) : m_Timer(io) { m_Timer.expires_at(init ? boost::posix_time::neg_infin : boost::posix_time::pos_infin); } -void AsioConditionVariable::Set() +void AsioEvent::Set() { m_Timer.expires_at(boost::posix_time::neg_infin); } -void AsioConditionVariable::Clear() +void AsioEvent::Clear() { m_Timer.expires_at(boost::posix_time::pos_infin); } -void AsioConditionVariable::Wait(boost::asio::yield_context yc) +void AsioEvent::Wait(boost::asio::yield_context yc) { boost::system::error_code ec; m_Timer.async_wait(yc[ec]); diff --git a/lib/base/io-engine.hpp b/lib/base/io-engine.hpp index 64831ff8c..049523e52 100644 --- a/lib/base/io-engine.hpp +++ b/lib/base/io-engine.hpp @@ -158,14 +158,14 @@ class TerminateIoThread : public std::exception }; /** - * Condition variable which doesn't block I/O threads + * Awaitable flag which doesn't block I/O threads, inspired by threading.Event from Python * * @ingroup base */ -class AsioConditionVariable +class AsioEvent { public: - AsioConditionVariable(boost::asio::io_context& io, bool init = false); + AsioEvent(boost::asio::io_context& io, bool init = false); void Set(); void Clear(); diff --git a/lib/icingadb/redisconnection.hpp b/lib/icingadb/redisconnection.hpp index acc6e4381..2038f797c 100644 --- a/lib/icingadb/redisconnection.hpp +++ b/lib/icingadb/redisconnection.hpp @@ -262,7 +262,7 @@ namespace icinga std::set m_SuppressedQueryKinds; // Indicate that there's something to send/receive - AsioConditionVariable m_QueuedWrites, m_QueuedReads; + AsioEvent m_QueuedWrites, m_QueuedReads; std::function m_ConnectedCallback; diff --git a/lib/remote/jsonrpcconnection.hpp b/lib/remote/jsonrpcconnection.hpp index ef83dce1b..826d3b46a 100644 --- a/lib/remote/jsonrpcconnection.hpp +++ b/lib/remote/jsonrpcconnection.hpp @@ -75,8 +75,8 @@ private: double m_Seen; boost::asio::io_context::strand m_IoStrand; std::vector m_OutgoingMessagesQueue; - AsioConditionVariable m_OutgoingMessagesQueued; - AsioConditionVariable m_WriterDone; + AsioEvent m_OutgoingMessagesQueued; + AsioEvent m_WriterDone; Atomic m_ShuttingDown; boost::asio::deadline_timer m_CheckLivenessTimer, m_HeartbeatTimer; From c566f6dc31a39c40fdd66b463d4753d58d0580fc Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 31 Mar 2025 10:44:22 +0200 Subject: [PATCH 276/415] ApiFunction: store own name --- lib/remote/apifunction.cpp | 4 ++-- lib/remote/apifunction.hpp | 10 ++++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/lib/remote/apifunction.cpp b/lib/remote/apifunction.cpp index 89e1d8734..f153dcb46 100644 --- a/lib/remote/apifunction.cpp +++ b/lib/remote/apifunction.cpp @@ -5,8 +5,8 @@ using namespace icinga; -ApiFunction::ApiFunction(Callback function) - : m_Callback(std::move(function)) +ApiFunction::ApiFunction(const char* name, Callback function) + : m_Name(name), m_Callback(std::move(function)) { } Value ApiFunction::Invoke(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& arguments) diff --git a/lib/remote/apifunction.hpp b/lib/remote/apifunction.hpp index 5a99db518..ea8b7cf1e 100644 --- a/lib/remote/apifunction.hpp +++ b/lib/remote/apifunction.hpp @@ -25,7 +25,12 @@ public: typedef std::function Callback; - ApiFunction(Callback function); + ApiFunction(const char* name, Callback function); + + const char* GetName() const noexcept + { + return m_Name; + } Value Invoke(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& arguments); @@ -33,6 +38,7 @@ public: static void Register(const String& name, const ApiFunction::Ptr& function); private: + const char* m_Name; Callback m_Callback; }; @@ -49,7 +55,7 @@ public: #define REGISTER_APIFUNCTION(name, ns, callback) \ INITIALIZE_ONCE([]() { \ - ApiFunction::Ptr func = new ApiFunction(callback); \ + ApiFunction::Ptr func = new ApiFunction(#ns "::" #name, callback); \ ApiFunctionRegistry::GetInstance()->Register(#ns "::" #name, func); \ }) From 3dd7b1580848a8ba41683c0ca0a797034e087bba Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 1 Apr 2025 15:38:38 +0200 Subject: [PATCH 277/415] Count incoming messages per type and endpoint --- lib/remote/endpoint.cpp | 13 +++++++++++++ lib/remote/endpoint.hpp | 8 ++++++++ lib/remote/jsonrpcconnection.cpp | 4 ++++ 3 files changed, 25 insertions(+) diff --git a/lib/remote/endpoint.cpp b/lib/remote/endpoint.cpp index e534fc178..48621432c 100644 --- a/lib/remote/endpoint.cpp +++ b/lib/remote/endpoint.cpp @@ -2,6 +2,7 @@ #include "remote/endpoint.hpp" #include "remote/endpoint-ti.cpp" +#include "remote/apifunction.hpp" #include "remote/apilistener.hpp" #include "remote/jsonrpcconnection.hpp" #include "remote/zone.hpp" @@ -35,6 +36,13 @@ void Endpoint::SetCachedZone(const Zone::Ptr& zone) m_Zone = zone; } +Endpoint::Endpoint() +{ + for (auto& [name, afunc] : ApiFunctionRegistry::GetInstance()->GetItems()) { + m_MessageCounters.emplace(afunc, 0); + } +} + void Endpoint::AddClient(const JsonRpcConnection::Ptr& client) { bool was_master = ApiListener::GetInstance()->IsMaster(); @@ -117,6 +125,11 @@ void Endpoint::AddMessageReceived(int bytes) SetLastMessageReceived(time); } +void Endpoint::AddMessageReceived(const intrusive_ptr& method) +{ + m_MessageCounters.at(method).fetch_add(1, std::memory_order_relaxed); +} + double Endpoint::GetMessagesSentPerSecond() const { return m_MessagesSent.CalculateRate(Utility::GetTime(), 60); diff --git a/lib/remote/endpoint.hpp b/lib/remote/endpoint.hpp index d641c2c6b..2ccfc053f 100644 --- a/lib/remote/endpoint.hpp +++ b/lib/remote/endpoint.hpp @@ -5,12 +5,16 @@ #include "remote/i2-remote.hpp" #include "remote/endpoint-ti.hpp" +#include "base/atomic.hpp" #include "base/ringbuffer.hpp" +#include #include +#include namespace icinga { +class ApiFunction; class JsonRpcConnection; class Zone; @@ -28,6 +32,8 @@ public: static boost::signals2::signal&)> OnConnected; static boost::signals2::signal&)> OnDisconnected; + Endpoint(); + void AddClient(const intrusive_ptr& client); void RemoveClient(const intrusive_ptr& client); std::set > GetClients() const; @@ -42,6 +48,7 @@ public: void AddMessageSent(int bytes); void AddMessageReceived(int bytes); + void AddMessageReceived(const intrusive_ptr& method); double GetMessagesSentPerSecond() const override; double GetMessagesReceivedPerSecond() const override; @@ -56,6 +63,7 @@ private: mutable std::mutex m_ClientsLock; std::set > m_Clients; intrusive_ptr m_Zone; + std::unordered_map, Atomic> m_MessageCounters; mutable RingBuffer m_MessagesSent{60}; mutable RingBuffer m_MessagesReceived{60}; diff --git a/lib/remote/jsonrpcconnection.cpp b/lib/remote/jsonrpcconnection.cpp index 889d4452c..e7dbb99e2 100644 --- a/lib/remote/jsonrpcconnection.cpp +++ b/lib/remote/jsonrpcconnection.cpp @@ -351,6 +351,10 @@ void JsonRpcConnection::MessageHandler(const Dictionary::Ptr& message) Log(LogNotice, "JsonRpcConnection") << "Call to non-existent function '" << method << "' from endpoint '" << m_Identity << "'."; } else { + if (m_Endpoint) { + m_Endpoint->AddMessageReceived(afunc); + } + Dictionary::Ptr params = message->Get("params"); if (params) resultMessage->Set("result", afunc->Invoke(origin, params)); From 98d097517b70cc5af6354869f1201ecd59f86079 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 1 Apr 2025 16:45:58 +0200 Subject: [PATCH 278/415] Introduce Endpoint#messages_received_per_type --- lib/remote/endpoint.cpp | 13 +++++++++++++ lib/remote/endpoint.hpp | 2 ++ lib/remote/endpoint.ti | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/lib/remote/endpoint.cpp b/lib/remote/endpoint.cpp index 48621432c..db908e558 100644 --- a/lib/remote/endpoint.cpp +++ b/lib/remote/endpoint.cpp @@ -149,3 +149,16 @@ double Endpoint::GetBytesReceivedPerSecond() const { return m_BytesReceived.CalculateRate(Utility::GetTime(), 60); } + +Dictionary::Ptr Endpoint::GetMessagesReceivedPerType() const +{ + DictionaryData result; + + for (auto& [afunc, cnt] : m_MessageCounters) { + if (auto v (cnt.load(std::memory_order_relaxed)); v) { + result.emplace_back(afunc->GetName(), v); + } + } + + return new Dictionary(std::move(result)); +} diff --git a/lib/remote/endpoint.hpp b/lib/remote/endpoint.hpp index 2ccfc053f..09ad8a4fd 100644 --- a/lib/remote/endpoint.hpp +++ b/lib/remote/endpoint.hpp @@ -56,6 +56,8 @@ public: double GetBytesSentPerSecond() const override; double GetBytesReceivedPerSecond() const override; + Dictionary::Ptr GetMessagesReceivedPerType() const override; + protected: void OnAllConfigLoaded() override; diff --git a/lib/remote/endpoint.ti b/lib/remote/endpoint.ti index 78551ecf0..2fa874b5e 100644 --- a/lib/remote/endpoint.ti +++ b/lib/remote/endpoint.ti @@ -54,6 +54,10 @@ class Endpoint : ConfigObject [no_user_modify, no_storage] double bytes_received_per_second { get; }; + + [no_user_modify, no_storage] Dictionary::Ptr messages_received_per_type { + get; + }; }; } From cc48c924aee893f237b43baf8015bc17d8c393d8 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Tue, 29 Apr 2025 11:10:36 +0200 Subject: [PATCH 279/415] Load Notification objects after User and UserGroup Notification objects can refer User and Group objects similar to how they can refer Host and Service objects, so that dependency feels quite natural. Note that for evaluating most configuration, this order doesn't really matter, the configuration will successfully evaluate in either case, the difference can be noticed mainly in more advanced configurations, for example when dynamically assigning user based on their groups. When accessing user objects from the Notification object definition (like in the following example), without this change, only groups configured directly in groups attribute of User objects are visible and those added via assign clauses in UserGroup objects are missing. With this commit, these are also visible. apply Notification "n" to Host { for (var u in get_objects(User)) { log(u.name + " -> " + Json.encode(u.groups)) } # [...] } --- lib/icinga/notification.ti | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/icinga/notification.ti b/lib/icinga/notification.ti index be0784613..a35c4cffb 100644 --- a/lib/icinga/notification.ti +++ b/lib/icinga/notification.ti @@ -22,6 +22,8 @@ class Notification : CustomVarObject < NotificationNameComposer { load_after Host; load_after Service; + load_after User; + load_after UserGroup; [config, protected, required, navigation] name(NotificationCommand) command (CommandRaw) { navigate {{{ From 41c0148e72e47e1e9af923badd51241b48c9bb15 Mon Sep 17 00:00:00 2001 From: Silas <67681686+Tqnsls@users.noreply.github.com> Date: Tue, 29 Apr 2025 11:56:17 +0200 Subject: [PATCH 280/415] Include "procs-to-show" to ITL check_load command --- doc/10-icinga-template-library.md | 21 +++++++++++---------- itl/command-plugins.conf | 4 ++++ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md index 773a9924e..6ae7a926f 100644 --- a/doc/10-icinga-template-library.md +++ b/doc/10-icinga-template-library.md @@ -913,16 +913,17 @@ tests the current system load average. Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -----------------|-------------- -load_wload1 | **Optional.** The 1-minute warning threshold. Defaults to 5. -load_wload5 | **Optional.** The 5-minute warning threshold. Defaults to 4. -load_wload15 | **Optional.** The 15-minute warning threshold. Defaults to 3. -load_cload1 | **Optional.** The 1-minute critical threshold. Defaults to 10. -load_cload5 | **Optional.** The 5-minute critical threshold. Defaults to 6. -load_cload15 | **Optional.** The 15-minute critical threshold. Defaults to 4. -load_percpu | **Optional.** Divide the load averages by the number of CPUs (when possible). Defaults to false. -load_extra_opts | **Optional.** Read extra plugin options from an ini file. +Name | Description +-------------------|-------------- +load_wload1 | **Optional.** The 1-minute warning threshold. Defaults to 5. +load_wload5 | **Optional.** The 5-minute warning threshold. Defaults to 4. +load_wload15 | **Optional.** The 15-minute warning threshold. Defaults to 3. +load_cload1 | **Optional.** The 1-minute critical threshold. Defaults to 10. +load_cload5 | **Optional.** The 5-minute critical threshold. Defaults to 6. +load_cload15 | **Optional.** The 15-minute critical threshold. Defaults to 4. +load_percpu | **Optional.** Divide the load averages by the number of CPUs (when possible). Defaults to false. +load_procs_to_show | **Optional.** Number of processes to show when printing the top consuming processes. (Default value is 0) +load_extra_opts | **Optional.** Read extra plugin options from an ini file. ### mailq diff --git a/itl/command-plugins.conf b/itl/command-plugins.conf index 06246bbe6..ed8fa5b3c 100644 --- a/itl/command-plugins.conf +++ b/itl/command-plugins.conf @@ -2000,6 +2000,10 @@ object CheckCommand "load" { set_if = "$load_percpu$" description = "Divide the load averages by the number of CPUs (when possible)" } + "-n" = { + value = "$load_procs_to_show$" + description = "Number of processes to show when printing the top consuming processes. (Default value is 0)" + } } vars.load_wload1 = 5.0 From 88b2831bfaa184f964bc79007e45faafcf34e33b Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Wed, 23 Apr 2025 11:54:37 +0200 Subject: [PATCH 281/415] Improve the documentation for generating core dumps --- doc/21-development.md | 139 +++++++++++++++++++++++++++++------------- 1 file changed, 98 insertions(+), 41 deletions(-) diff --git a/doc/21-development.md b/doc/21-development.md index bf458cfb2..2f17c8e83 100644 --- a/doc/21-development.md +++ b/doc/21-development.md @@ -267,73 +267,130 @@ $3 = std::vector of length 11, capacity 16 = {{static NPos = 1844674407370955161 ### Core Dump -When the Icinga 2 daemon crashes with a `SIGSEGV` signal -a core dump file should be written. This will help -developers to analyze and fix the problem. +When the Icinga 2 daemon is terminated by `SIGSEGV` or `SIGABRT`, a core dump file +should be written. This will help developers to analyze and fix the problem. -#### Core Dump File Size Limit +#### Core Dump Kernel Pattern -This requires setting the core dump file size to `unlimited`. +Core dumps are generated according to the format specified in +`/proc/sys/kernel/core_pattern`. This can either be a path relative to the +directory the program was started in, an absolute path or a pipe to a different +program. +For more information see the [core(5)](https://man7.org/linux/man-pages/man5/core.5.html) man page. -##### Systemd +#### Systemd Coredumpctl +Most distributions offer systemd's coredumpctl either by default or as a package. +Distributions that offer it by default include RHEL and SLES, on others like +Debian or Ubuntu it can be installed via the `systemd-coredump` package. +When set up correctly, `core_pattern` will look something like this: ``` -systemctl edit icinga2.service - -[Service] -... -LimitCORE=infinity - -systemctl daemon-reload - -systemctl restart icinga2 +# cat /proc/sys/kernel/core_pattern +|/usr/lib/systemd/systemd-coredump %P %u %g %s %t %c %h` ``` -##### Init Script +You can look at the generated core dumps with the `coredumpctl list` command. +You can show information, including a stack trace using +`coredumpctl show icinga2 -1` and retrieve the actual core dump file with +`coredumpctl dump icinga2 -1 --output `. +For further information on how to configure and use coredumpctl, read the man pages +[coredumpctl(1)](https://man7.org/linux/man-pages/man1/coredumpctl.1.html) and +[coredump.conf(5)](https://man7.org/linux/man-pages/man5/coredump.conf.5.html). + +#### Ubuntu Apport + +Ubuntu uses their own application `apport` to record core dumps. When it is +enabled, your `core_pattern` will look like this: ``` -vim /etc/init.d/icinga2 -... -ulimit -c unlimited - -service icinga2 restart +# cat /proc/sys/kernel/core_pattern +|/usr/share/apport/apport -p%p -s%s -c%c -d%d -P%P -u%u -g%g -- %E ``` -##### Verify - -Verify that the Icinga 2 process core file size limit is set to `unlimited`. +Apport is unsuitable for development work, because by default it only works +with Ubuntu packages and it has a rather complicated interface for retrieving +the core dump. So unless you rely on Apport for some other workflow, systemd's +coredumpctl is a much better option and is available on Ubuntu in the +`systemd-coredump` package that can replace Apport on your system with no +further setup required. +If you still want to use Apport however, to set it up to work with unpackaged programs, +add the following (create the file if it doesn't exist) to `/etc/apport/settings`: ``` -for pid in $(pidof icinga2); do cat /proc/$pid/limits; done - -... -Max core file size unlimited unlimited bytes +[main] +unpackaged=true +``` +and restart Apport: +``` +systemctl restart apport.service ``` +When the program crashes you can then find an Apport crash report in `/var/crash/` +that you can read with the interactive `apport-cli` command. To extract the core +dump you run `apport-unpack /var/crash/ ` which then +saves a `/CoreDump` file that contains the actual core dump. -#### Core Dump Kernel Format +#### Directly to a File -The Icinga 2 daemon runs with the SUID bit set. Therefore you need -to explicitly enable core dumps for SUID on Linux. +If coredumpctl is not available, simply writing the core dump directly to a file +is also sufficient. You can set up your `core_pattern` to write a file to a +suitable path: ```bash -sysctl -w fs.suid_dumpable=2 -``` - -Adjust the coredump kernel format and file location on Linux: - -```bash -sysctl -w kernel.core_pattern=/var/lib/cores/core.%e.%p - +sysctl -w kernel.core_pattern=/var/lib/cores/core.%e.%p.%h.%t install -m 1777 -d /var/lib/cores ``` -MacOS: +If you want to make this setting permanent you can also add a file to +`/etc/sysctl.d`, named something like `80-coredumps.conf`: +``` +kernel.core_pattern = /var/lib/cores/core.%e.%p.%h.%t +``` + +This will create core dump files in `/var/lib/cores` where `%e` is the truncated +name of the program, `%p` is the programs PID, `%h` is the hostname, and `%t` a +timestamp. + +Note that unlike the other methods this requires the core size limit to be set +for the process. When starting Icinga 2 via systemd you can set it to unlimited +by adding the following to `/etc/systemd/system/icinga2.service.d/limits.conf`: +``` +[Service] +LimitCORE=infinity +``` + +Then reload and restart icinga: +```bash +systemctl daemon-reload +systemctl restart icinga2.service +``` + +Alternatively you edit and reload in one step: +```bash +systemctl edit --drop-in=limits icinga2.service` +``` + +When using an init script or starting manually, you need to run `ulimit -c unlimited` +before starting the program: +```bash +ulimit -c unlimited +./icinga2 daemon +``` + +To verify that the limit has been set to `unlimited` run the following: +```bash +for pid in $(pidof icinga2); do cat /proc/$pid/limits; done +``` +And look for the line: +``` +Max core file size unlimited unlimited bytes +``` + +#### MacOS ```bash sysctl -w kern.corefile=/cores/core.%P - chmod 777 /cores ``` From 6f5725576c072278e1f4e2dfffe7dc7d628df0e2 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Mon, 5 May 2025 13:27:08 +0200 Subject: [PATCH 282/415] GHA: Fix amazonlinux:2023 MariaDB package Since recently, the amazonlinux:2023 job in the Linux action fails due to conflichting 'mariadb1*-devel' packages. > package mariadb1011-devel-3:10.11.11-1.amzn2023.0.1.x86_64 from amazonlinux conflicts with mariadb105-devel provided by mariadb105-devel-3:10.5.16-1.amzn2023.0.7.x86_64 from amazonlinux It seems like Amazon Linux added mariadb1011 packages next to mariadb105 packages, resulting in a conflict due to the wildcard. On prior runs, the mariadb105 packages was installed. This change installs mariadb-connector-c-devel instead of a specific mariadb1*-devel package, as suggested by the package description. --- .github/workflows/linux.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linux.bash b/.github/workflows/linux.bash index 7eabec9a3..5cb1d6f97 100755 --- a/.github/workflows/linux.bash +++ b/.github/workflows/linux.bash @@ -39,7 +39,7 @@ case "$DISTRO" in amazonlinux:20*) dnf install -y amazon-rpm-config bison cmake flex gcc-c++ ninja-build \ - {boost,libedit,mariadb1\*,ncurses,openssl,postgresql,systemd}-devel + {boost,libedit,mariadb-connector-c,ncurses,openssl,postgresql,systemd}-devel ;; debian:*|ubuntu:*) From 89f12c23230d682893573f7e948e34517985f700 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 6 Mar 2025 10:51:54 +0100 Subject: [PATCH 283/415] Notification: Reset `no_more_notifications` only on recovery --- lib/icinga/notification.cpp | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/icinga/notification.cpp b/lib/icinga/notification.cpp index bd3158ced..b16b43e95 100644 --- a/lib/icinga/notification.cpp +++ b/lib/icinga/notification.cpp @@ -223,6 +223,30 @@ void Notification::ResetNotificationNumber() SetNotificationNumber(0); } +/** + * Check whether the given notification type is a Recovery or FlappingEnd notification and the Checkable object + * has already recovered. + * + * For the latter case, if the Checkable has recovered while it was in a Flapping state, the recovery notification + * will be silently discarded and leave some internal states of the Notification object that depend on it in an + * inconsistent state. So, use this helper function whether to reset one of these states. + * + * @param checkable The checkable object the notification is for + * @param cr The current check result passed to the notification + * @param type The requested notification type + * + * @return bool + */ +static bool IsRecoveryOrFlappingEndAndCheckableIsOK(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, NotificationType type) +{ + if (type == NotificationRecovery) { + return true; + } + + // Check whether missed the Checkable recovery because of its Flapping state. + return type == NotificationFlappingEnd && cr && checkable->IsStateOK(cr->GetState()); +} + void Notification::BeginExecuteNotification(NotificationType type, const CheckResult::Ptr& cr, bool force, bool reminder, const String& author, const String& text) { String notificationName = GetName(); @@ -389,7 +413,7 @@ void Notification::BeginExecuteNotification(NotificationType type, const CheckRe if (type == NotificationProblem && GetInterval() <= 0) SetNoMoreNotifications(true); - else if (type != NotificationCustom) + else if (IsRecoveryOrFlappingEndAndCheckableIsOK(checkable, cr, type)) SetNoMoreNotifications(false); if (type == NotificationProblem && GetInterval() > 0) From 86365a4e2b6708321234bd7f4a2e3cd1700b6e81 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 12 Mar 2025 14:18:19 +0100 Subject: [PATCH 284/415] Notification: Clear last notified state per user on flapping end as well --- lib/icinga/notification.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/icinga/notification.cpp b/lib/icinga/notification.cpp index b16b43e95..b6e9cd511 100644 --- a/lib/icinga/notification.cpp +++ b/lib/icinga/notification.cpp @@ -257,15 +257,18 @@ void Notification::BeginExecuteNotification(NotificationType type, const CheckRe << "notifications of type '" << notificationTypeName << "' for notification object '" << notificationName << "'."; - if (type == NotificationRecovery) { + Checkable::Ptr checkable = GetCheckable(); + + // Clear the last notified problem state per user if we're sending a recovery notification or if we're sending a + // flapping end notification and the checkable is already in an OK state. This is necessary since we might have + // missed the recovery notification due to the flapping state. + if (IsRecoveryOrFlappingEndAndCheckableIsOK(checkable, cr, type)) { auto states (GetLastNotifiedStatePerUser()); states->Clear(); OnLastNotifiedStatePerUserCleared(this, nullptr); } - Checkable::Ptr checkable = GetCheckable(); - if (!force) { TimePeriod::Ptr tp = GetPeriod(); From 91663268766c8a816ca863f3bdbe2524a83b45b3 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 12 Mar 2025 14:19:22 +0100 Subject: [PATCH 285/415] Notification: Reset notified problem users on flapping end as well --- lib/icinga/notification.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/icinga/notification.cpp b/lib/icinga/notification.cpp index b6e9cd511..5b2964a84 100644 --- a/lib/icinga/notification.cpp +++ b/lib/icinga/notification.cpp @@ -520,7 +520,7 @@ void Notification::BeginExecuteNotification(NotificationType type, const CheckRe } /* if this was a recovery notification, reset all notified users */ - if (type == NotificationRecovery) + if (IsRecoveryOrFlappingEndAndCheckableIsOK(checkable, cr, type)) notifiedProblemUsers->Clear(); /* used in db_ido for notification history */ From 4596b44171797a0c7691194f75116998b3bd27e3 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 12 Mar 2025 14:20:01 +0100 Subject: [PATCH 286/415] Reset `no_more_notifications` on filter mismatch correctly Previously, if you enable flapping for a Checkable but the corresponding `Notification` object does not have `FlappingStart` or `FlappingEnd` types set, the `no_more_notifications` flag wasn't reset to false again. This commit ensures that this flag is always reset on `Recovery` even the type filter does not match including when we miss the `Recovery` due to Flapping state. --- lib/icinga/notification.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/icinga/notification.cpp b/lib/icinga/notification.cpp index 5b2964a84..23c06e480 100644 --- a/lib/icinga/notification.cpp +++ b/lib/icinga/notification.cpp @@ -365,7 +365,7 @@ void Notification::BeginExecuteNotification(NotificationType type, const CheckRe */ { ObjectLock olock(this); - if (type == NotificationRecovery && GetInterval() <= 0) + if (GetInterval() <= 0 && IsRecoveryOrFlappingEndAndCheckableIsOK(checkable, cr, type)) SetNoMoreNotifications(false); } From 241e1f943756d37ccd726a6d37a6776ee9b9d43c Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 5 May 2025 11:17:35 +0200 Subject: [PATCH 287/415] Fix broken SELinux policy on Fedora `>=41` systems `sbindir` can be both `/usr/sbin` or `/usr/bin` depending on the used OS hence we need to make sure that this pattern matches on both paths. --- tools/selinux/icinga2.fc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tools/selinux/icinga2.fc b/tools/selinux/icinga2.fc index 325728d31..1d74e3e6f 100644 --- a/tools/selinux/icinga2.fc +++ b/tools/selinux/icinga2.fc @@ -6,7 +6,8 @@ /etc/icinga2/scripts(/.*)? -- gen_context(system_u:object_r:nagios_notification_plugin_exec_t,s0) -/usr/sbin/icinga2 -- gen_context(system_u:object_r:icinga2_exec_t,s0) +/usr/s?bin/icinga2 -- gen_context(system_u:object_r:icinga2_exec_t,s0) + /usr/lib/icinga2/sbin/icinga2 -- gen_context(system_u:object_r:icinga2_exec_t,s0) /usr/lib/icinga2/safe-reload -- gen_context(system_u:object_r:icinga2_exec_t,s0) From 7e65a60a5d69c59921a0a8fbf7866fb484a32e9f Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Fri, 9 May 2025 09:11:42 +0200 Subject: [PATCH 288/415] Fix PerfdataValue Counter Parsing Ensure that the counter unit of measurement, "c", is parsed correctly for performance data values again. A prior refactoring in 720a88c29a489cec91815af49755413202802d7a changed the parsing logic, resulting in an incorrect behavior for counter units. By passing the raw input into the l_CsUoMs map first, the "c" UoM is removed. Moving the explicit counter check before passing the raw unit into the map resolves this issue. Fixes #9540. --- lib/base/perfdatavalue.cpp | 9 +++++---- test/icinga-perfdata.cpp | 13 +++++++++++++ 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/lib/base/perfdatavalue.cpp b/lib/base/perfdatavalue.cpp index 217167e01..5c7c7622f 100644 --- a/lib/base/perfdatavalue.cpp +++ b/lib/base/perfdatavalue.cpp @@ -270,6 +270,11 @@ PerfdataValue::Ptr PerfdataValue::Parse(const String& perfdata) if (pos != String::NPos) unit = tokens[0].SubStr(pos, String::NPos); + // UoM.Out is an empty string for "c". So set counter before parsing. + if (unit == "c") { + counter = true; + } + double base; { @@ -295,10 +300,6 @@ PerfdataValue::Ptr PerfdataValue::Parse(const String& perfdata) } } - if (unit == "c") { - counter = true; - } - warn = ParseWarnCritMinMaxToken(tokens, 1, "warning"); crit = ParseWarnCritMinMaxToken(tokens, 2, "critical"); min = ParseWarnCritMinMaxToken(tokens, 3, "minimum"); diff --git a/test/icinga-perfdata.cpp b/test/icinga-perfdata.cpp index 68d0ea277..8bc97d4fa 100644 --- a/test/icinga-perfdata.cpp +++ b/test/icinga-perfdata.cpp @@ -285,6 +285,19 @@ BOOST_AUTO_TEST_CASE(uom) str = pv->Format(); BOOST_CHECK_EQUAL(str, "test=1W"); + + pv = PerfdataValue::Parse("test=42c"); + BOOST_CHECK(pv); + BOOST_CHECK_EQUAL(pv->GetValue(), 42); + BOOST_CHECK(pv->GetCounter()); + BOOST_CHECK_EQUAL(pv->GetUnit(), ""); + BOOST_CHECK_EQUAL(pv->GetCrit(), Empty); + BOOST_CHECK_EQUAL(pv->GetWarn(), Empty); + BOOST_CHECK_EQUAL(pv->GetMin(), Empty); + BOOST_CHECK_EQUAL(pv->GetMax(), Empty); + + str = pv->Format(); + BOOST_CHECK_EQUAL(str, "test=42c"); } BOOST_AUTO_TEST_CASE(warncritminmax) From d750bff193931e091101fb2a83e2b349dd564f14 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 8 Nov 2024 15:37:18 +0100 Subject: [PATCH 289/415] Notification: Fix incorrectly dropped recovery & ACK notifications Previously, recovery and ACK notifications were not delivered to users who weren't notified about the problem state while having a configured `Problem` type filter. However, since the type filter can also be configured on the `Notification` object level, this resulted to an incorrect behaviour. This PR changes the existing logic so that the recovery and ACK notifications gets dropped only if the `Problem` filter is configured on both the `User` and `Notification` object levels. --- lib/icinga/notification.cpp | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/lib/icinga/notification.cpp b/lib/icinga/notification.cpp index bd3158ced..087c2a19d 100644 --- a/lib/icinga/notification.cpp +++ b/lib/icinga/notification.cpp @@ -428,22 +428,17 @@ void Notification::BeginExecuteNotification(NotificationType type, const CheckRe continue; } - /* on recovery, check if user was notified before */ - if (type == NotificationRecovery) { - if (!notifiedProblemUsers->Contains(userName) && (NotificationProblem & user->GetTypeFilter())) { + /* on acknowledgement/recovery, check if user was notified before */ + if (type == NotificationAcknowledgement || type == NotificationRecovery) { + // Do not notify the user about the ACK/recovery of a problem state that they have not been notified + // about, unless they are explicitly configured to receive ACK/recovery notifications but no problem + // ones, or this Notification instance isn't allowed to send problem notifications (doesn't contain + // the 'Problem' type filter). + if (!notifiedProblemUsers->Contains(userName) && NotificationProblem & user->GetTypeFilter() & GetTypeFilter()) { Log(LogNotice, "Notification") << "Notification object '" << notificationName << "': We did not notify user '" << userName - << "' (Problem types enabled) for a problem before. Not sending Recovery notification."; - continue; - } - } - - /* on acknowledgement, check if user was notified before */ - if (type == NotificationAcknowledgement) { - if (!notifiedProblemUsers->Contains(userName) && (NotificationProblem & user->GetTypeFilter())) { - Log(LogNotice, "Notification") - << "Notification object '" << notificationName << "': We did not notify user '" << userName - << "' (Problem types enabled) for a problem before. Not sending acknowledgement notification."; + << "' (Problem types enabled) for a problem before. Not sending " + << (type == NotificationRecovery ? "Recovery" : "acknowledgement") << " notification."; continue; } } From 2e19fce31d972e4968ed9beba02ed695fde9ebc9 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 25 Apr 2025 12:08:08 +0200 Subject: [PATCH 290/415] Remove some superfluous if statements They're just useless, since a `CheckResult` handler is never going to be called without a check result and a checkable can't exist without a checkcommand. --- lib/perfdata/elasticsearchwriter.cpp | 37 ++++----------------- lib/perfdata/gelfwriter.cpp | 48 ++++++++-------------------- 2 files changed, 21 insertions(+), 64 deletions(-) diff --git a/lib/perfdata/elasticsearchwriter.cpp b/lib/perfdata/elasticsearchwriter.cpp index eaa19da59..39e411813 100644 --- a/lib/perfdata/elasticsearchwriter.cpp +++ b/lib/perfdata/elasticsearchwriter.cpp @@ -272,22 +272,12 @@ void ElasticsearchWriter::InternalCheckResultHandler(const Checkable::Ptr& check fields->Set("max_check_attempts", checkable->GetMaxCheckAttempts()); fields->Set("reachable", checkable->IsReachable()); + fields->Set("check_command", checkable->GetCheckCommand()->GetName()); - CheckCommand::Ptr commandObj = checkable->GetCheckCommand(); - - if (commandObj) - fields->Set("check_command", commandObj->GetName()); - - double ts = Utility::GetTime(); - - if (cr) { - AddCheckResult(fields, checkable, cr); - ts = cr->GetExecutionEnd(); - } - + AddCheckResult(fields, checkable, cr); AddTemplateTags(fields, checkable, cr); - Enqueue(checkable, "checkresult", fields, ts); + Enqueue(checkable, "checkresult", fields, cr->GetExecutionEnd()); } void ElasticsearchWriter::StateChangeHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, StateType type) @@ -325,21 +315,12 @@ void ElasticsearchWriter::StateChangeHandlerInternal(const Checkable::Ptr& check fields->Set("last_hard_state", host->GetLastHardState()); } - CheckCommand::Ptr commandObj = checkable->GetCheckCommand(); - - if (commandObj) - fields->Set("check_command", commandObj->GetName()); - - double ts = Utility::GetTime(); - - if (cr) { - AddCheckResult(fields, checkable, cr); - ts = cr->GetExecutionEnd(); - } + fields->Set("check_command", checkable->GetCheckCommand()->GetName()); + AddCheckResult(fields, checkable, cr); AddTemplateTags(fields, checkable, cr); - Enqueue(checkable, "statechange", fields, ts); + Enqueue(checkable, "statechange", fields, cr->GetExecutionEnd()); } void ElasticsearchWriter::NotificationSentToAllUsersHandler(const Notification::Ptr& notification, @@ -396,11 +377,7 @@ void ElasticsearchWriter::NotificationSentToAllUsersHandlerInternal(const Notifi fields->Set("notification_type", notificationTypeString); fields->Set("author", author); fields->Set("text", text); - - CheckCommand::Ptr commandObj = checkable->GetCheckCommand(); - - if (commandObj) - fields->Set("check_command", commandObj->GetName()); + fields->Set("check_command", checkable->GetCheckCommand()->GetName()); double ts = Utility::GetTime(); diff --git a/lib/perfdata/gelfwriter.cpp b/lib/perfdata/gelfwriter.cpp index c5b2bbd13..35541ac60 100644 --- a/lib/perfdata/gelfwriter.cpp +++ b/lib/perfdata/gelfwriter.cpp @@ -306,22 +306,15 @@ void GelfWriter::CheckResultHandlerInternal(const Checkable::Ptr& checkable, con fields->Set("_reachable", checkable->IsReachable()); CheckCommand::Ptr checkCommand = checkable->GetCheckCommand(); + fields->Set("_check_command", checkCommand->GetName()); - if (checkCommand) - fields->Set("_check_command", checkCommand->GetName()); + fields->Set("_latency", cr->CalculateLatency()); + fields->Set("_execution_time", cr->CalculateExecutionTime()); + fields->Set("short_message", CompatUtility::GetCheckResultOutput(cr)); + fields->Set("full_message", cr->GetOutput()); + fields->Set("_check_source", cr->GetCheckSource()); - double ts = Utility::GetTime(); - - if (cr) { - fields->Set("_latency", cr->CalculateLatency()); - fields->Set("_execution_time", cr->CalculateExecutionTime()); - fields->Set("short_message", CompatUtility::GetCheckResultOutput(cr)); - fields->Set("full_message", cr->GetOutput()); - fields->Set("_check_source", cr->GetCheckSource()); - ts = cr->GetExecutionEnd(); - } - - if (cr && GetEnableSendPerfdata()) { + if (GetEnableSendPerfdata()) { Array::Ptr perfdata = cr->GetPerformanceData(); if (perfdata) { @@ -366,7 +359,7 @@ void GelfWriter::CheckResultHandlerInternal(const Checkable::Ptr& checkable, con } } - SendLogMessage(checkable, ComposeGelfMessage(fields, GetSource(), ts)); + SendLogMessage(checkable, ComposeGelfMessage(fields, GetSource(), cr->GetExecutionEnd())); } void GelfWriter::NotificationToUserHandler(const Notification::Ptr& notification, const Checkable::Ptr& checkable, @@ -430,11 +423,7 @@ void GelfWriter::NotificationToUserHandlerInternal(const Notification::Ptr& noti fields->Set("_command", commandName); fields->Set("_notification_type", notificationTypeString); fields->Set("_comment", authorComment); - - CheckCommand::Ptr commandObj = checkable->GetCheckCommand(); - - if (commandObj) - fields->Set("_check_command", commandObj->GetName()); + fields->Set("_check_command", checkable->GetCheckCommand()->GetName()); SendLogMessage(checkable, ComposeGelfMessage(fields, GetSource(), ts)); } @@ -478,21 +467,12 @@ void GelfWriter::StateChangeHandlerInternal(const Checkable::Ptr& checkable, con fields->Set("_last_hard_state", host->GetLastHardState()); } - CheckCommand::Ptr commandObj = checkable->GetCheckCommand(); + fields->Set("_check_command", checkable->GetCheckCommand()->GetName()); + fields->Set("short_message", CompatUtility::GetCheckResultOutput(cr)); + fields->Set("full_message", cr->GetOutput()); + fields->Set("_check_source", cr->GetCheckSource()); - if (commandObj) - fields->Set("_check_command", commandObj->GetName()); - - double ts = Utility::GetTime(); - - if (cr) { - fields->Set("short_message", CompatUtility::GetCheckResultOutput(cr)); - fields->Set("full_message", cr->GetOutput()); - fields->Set("_check_source", cr->GetCheckSource()); - ts = cr->GetExecutionEnd(); - } - - SendLogMessage(checkable, ComposeGelfMessage(fields, GetSource(), ts)); + SendLogMessage(checkable, ComposeGelfMessage(fields, GetSource(), cr->GetExecutionEnd())); } String GelfWriter::ComposeGelfMessage(const Dictionary::Ptr& fields, const String& source, double ts) From a589b87d6cc07b4da4712b4ec88f0bd458da0977 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 25 Apr 2025 12:14:31 +0200 Subject: [PATCH 291/415] Remove unused parameters --- lib/perfdata/elasticsearchwriter.cpp | 28 ++++++++++++-------------- lib/perfdata/elasticsearchwriter.hpp | 14 ++++++------- lib/perfdata/gelfwriter.cpp | 30 +++++++++++++--------------- lib/perfdata/gelfwriter.hpp | 14 ++++++------- 4 files changed, 39 insertions(+), 47 deletions(-) diff --git a/lib/perfdata/elasticsearchwriter.cpp b/lib/perfdata/elasticsearchwriter.cpp index 39e411813..ed4381931 100644 --- a/lib/perfdata/elasticsearchwriter.cpp +++ b/lib/perfdata/elasticsearchwriter.cpp @@ -101,13 +101,13 @@ void ElasticsearchWriter::Resume() CheckResultHandler(checkable, cr); }); m_HandleStateChanges = Checkable::OnStateChange.connect([this](const Checkable::Ptr& checkable, - const CheckResult::Ptr& cr, StateType type, const MessageOrigin::Ptr&) { - StateChangeHandler(checkable, cr, type); + const CheckResult::Ptr& cr, StateType, const MessageOrigin::Ptr&) { + StateChangeHandler(checkable, cr); }); - m_HandleNotifications = Checkable::OnNotificationSentToAllUsers.connect([this](const Notification::Ptr& notification, + m_HandleNotifications = Checkable::OnNotificationSentToAllUsers.connect([this](const Notification::Ptr&, const Checkable::Ptr& checkable, const std::set& users, const NotificationType& type, const CheckResult::Ptr& cr, const String& author, const String& text, const MessageOrigin::Ptr&) { - NotificationSentToAllUsersHandler(notification, checkable, users, type, cr, author, text); + NotificationSentToAllUsersHandler(checkable, users, type, cr, author, text); }); } @@ -280,15 +280,15 @@ void ElasticsearchWriter::InternalCheckResultHandler(const Checkable::Ptr& check Enqueue(checkable, "checkresult", fields, cr->GetExecutionEnd()); } -void ElasticsearchWriter::StateChangeHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, StateType type) +void ElasticsearchWriter::StateChangeHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) { if (IsPaused()) return; - m_WorkQueue.Enqueue([this, checkable, cr, type]() { StateChangeHandlerInternal(checkable, cr, type); }); + m_WorkQueue.Enqueue([this, checkable, cr]() { StateChangeHandlerInternal(checkable, cr); }); } -void ElasticsearchWriter::StateChangeHandlerInternal(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, StateType type) +void ElasticsearchWriter::StateChangeHandlerInternal(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) { AssertOnWorkQueue(); @@ -323,21 +323,19 @@ void ElasticsearchWriter::StateChangeHandlerInternal(const Checkable::Ptr& check Enqueue(checkable, "statechange", fields, cr->GetExecutionEnd()); } -void ElasticsearchWriter::NotificationSentToAllUsersHandler(const Notification::Ptr& notification, - const Checkable::Ptr& checkable, const std::set& users, NotificationType type, - const CheckResult::Ptr& cr, const String& author, const String& text) +void ElasticsearchWriter::NotificationSentToAllUsersHandler(const Checkable::Ptr& checkable, const std::set& users, + NotificationType type, const CheckResult::Ptr& cr, const String& author, const String& text) { if (IsPaused()) return; - m_WorkQueue.Enqueue([this, notification, checkable, users, type, cr, author, text]() { - NotificationSentToAllUsersHandlerInternal(notification, checkable, users, type, cr, author, text); + m_WorkQueue.Enqueue([this, checkable, users, type, cr, author, text]() { + NotificationSentToAllUsersHandlerInternal(checkable, users, type, cr, author, text); }); } -void ElasticsearchWriter::NotificationSentToAllUsersHandlerInternal(const Notification::Ptr& notification, - const Checkable::Ptr& checkable, const std::set& users, NotificationType type, - const CheckResult::Ptr& cr, const String& author, const String& text) +void ElasticsearchWriter::NotificationSentToAllUsersHandlerInternal(const Checkable::Ptr& checkable, const std::set& users, + NotificationType type, const CheckResult::Ptr& cr, const String& author, const String& text) { AssertOnWorkQueue(); diff --git a/lib/perfdata/elasticsearchwriter.hpp b/lib/perfdata/elasticsearchwriter.hpp index 9ecd0fd76..1f5eb60bf 100644 --- a/lib/perfdata/elasticsearchwriter.hpp +++ b/lib/perfdata/elasticsearchwriter.hpp @@ -42,16 +42,14 @@ private: void AddCheckResult(const Dictionary::Ptr& fields, const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); void AddTemplateTags(const Dictionary::Ptr& fields, const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); - void StateChangeHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, StateType type); - void StateChangeHandlerInternal(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, StateType type); + void StateChangeHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); + void StateChangeHandlerInternal(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); void CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); void InternalCheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); - void NotificationSentToAllUsersHandler(const Notification::Ptr& notification, - const Checkable::Ptr& checkable, const std::set& users, NotificationType type, - const CheckResult::Ptr& cr, const String& author, const String& text); - void NotificationSentToAllUsersHandlerInternal(const Notification::Ptr& notification, - const Checkable::Ptr& checkable, const std::set& users, NotificationType type, - const CheckResult::Ptr& cr, const String& author, const String& text); + void NotificationSentToAllUsersHandler(const Checkable::Ptr& checkable, const std::set& users, + NotificationType type, const CheckResult::Ptr& cr, const String& author, const String& text); + void NotificationSentToAllUsersHandlerInternal(const Checkable::Ptr& checkable, const std::set& users, + NotificationType type, const CheckResult::Ptr& cr, const String& author, const String& text); void Enqueue(const Checkable::Ptr& checkable, const String& type, const Dictionary::Ptr& fields, double ts); diff --git a/lib/perfdata/gelfwriter.cpp b/lib/perfdata/gelfwriter.cpp index 35541ac60..5f7ae059b 100644 --- a/lib/perfdata/gelfwriter.cpp +++ b/lib/perfdata/gelfwriter.cpp @@ -94,14 +94,14 @@ void GelfWriter::Resume() const CheckResult::Ptr& cr, const MessageOrigin::Ptr&) { CheckResultHandler(checkable, cr); }); - m_HandleNotifications = Checkable::OnNotificationSentToUser.connect([this](const Notification::Ptr& notification, - const Checkable::Ptr& checkable, const User::Ptr& user, const NotificationType& type, const CheckResult::Ptr& cr, + m_HandleNotifications = Checkable::OnNotificationSentToUser.connect([this](const Notification::Ptr&, + const Checkable::Ptr& checkable, const User::Ptr&, const NotificationType& type, const CheckResult::Ptr& cr, const String& author, const String& commentText, const String& commandName, const MessageOrigin::Ptr&) { - NotificationToUserHandler(notification, checkable, user, type, cr, author, commentText, commandName); + NotificationToUserHandler(checkable, type, cr, author, commentText, commandName); }); m_HandleStateChanges = Checkable::OnStateChange.connect([this](const Checkable::Ptr& checkable, - const CheckResult::Ptr& cr, StateType type, const MessageOrigin::Ptr&) { - StateChangeHandler(checkable, cr, type); + const CheckResult::Ptr& cr, StateType, const MessageOrigin::Ptr&) { + StateChangeHandler(checkable, cr); }); } @@ -362,21 +362,19 @@ void GelfWriter::CheckResultHandlerInternal(const Checkable::Ptr& checkable, con SendLogMessage(checkable, ComposeGelfMessage(fields, GetSource(), cr->GetExecutionEnd())); } -void GelfWriter::NotificationToUserHandler(const Notification::Ptr& notification, const Checkable::Ptr& checkable, - const User::Ptr& user, NotificationType notificationType, CheckResult::Ptr const& cr, - const String& author, const String& commentText, const String& commandName) +void GelfWriter::NotificationToUserHandler(const Checkable::Ptr& checkable, NotificationType notificationType, + CheckResult::Ptr const& cr, const String& author, const String& commentText, const String& commandName) { if (IsPaused()) return; - m_WorkQueue.Enqueue([this, notification, checkable, user, notificationType, cr, author, commentText, commandName]() { - NotificationToUserHandlerInternal(notification, checkable, user, notificationType, cr, author, commentText, commandName); + m_WorkQueue.Enqueue([this, checkable, notificationType, cr, author, commentText, commandName]() { + NotificationToUserHandlerInternal(checkable, notificationType, cr, author, commentText, commandName); }); } -void GelfWriter::NotificationToUserHandlerInternal(const Notification::Ptr& notification, const Checkable::Ptr& checkable, - const User::Ptr& user, NotificationType notificationType, CheckResult::Ptr const& cr, - const String& author, const String& commentText, const String& commandName) +void GelfWriter::NotificationToUserHandlerInternal(const Checkable::Ptr& checkable, NotificationType notificationType, + CheckResult::Ptr const& cr, const String& author, const String& commentText, const String& commandName) { AssertOnWorkQueue(); @@ -428,15 +426,15 @@ void GelfWriter::NotificationToUserHandlerInternal(const Notification::Ptr& noti SendLogMessage(checkable, ComposeGelfMessage(fields, GetSource(), ts)); } -void GelfWriter::StateChangeHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, StateType type) +void GelfWriter::StateChangeHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) { if (IsPaused()) return; - m_WorkQueue.Enqueue([this, checkable, cr, type]() { StateChangeHandlerInternal(checkable, cr, type); }); + m_WorkQueue.Enqueue([this, checkable, cr]() { StateChangeHandlerInternal(checkable, cr); }); } -void GelfWriter::StateChangeHandlerInternal(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, StateType type) +void GelfWriter::StateChangeHandlerInternal(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) { AssertOnWorkQueue(); diff --git a/lib/perfdata/gelfwriter.hpp b/lib/perfdata/gelfwriter.hpp index ce9ee3545..b1dc90796 100644 --- a/lib/perfdata/gelfwriter.hpp +++ b/lib/perfdata/gelfwriter.hpp @@ -41,14 +41,12 @@ private: void CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); void CheckResultHandlerInternal(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); - void NotificationToUserHandler(const Notification::Ptr& notification, const Checkable::Ptr& checkable, - const User::Ptr& user, NotificationType notificationType, const CheckResult::Ptr& cr, - const String& author, const String& commentText, const String& commandName); - void NotificationToUserHandlerInternal(const Notification::Ptr& notification, const Checkable::Ptr& checkable, - const User::Ptr& user, NotificationType notification_type, const CheckResult::Ptr& cr, - const String& author, const String& comment_text, const String& command_name); - void StateChangeHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, StateType type); - void StateChangeHandlerInternal(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, StateType type); + void NotificationToUserHandler(const Checkable::Ptr& checkable, NotificationType notificationType, + const CheckResult::Ptr& cr, const String& author, const String& commentText, const String& commandName); + void NotificationToUserHandlerInternal(const Checkable::Ptr& checkable, NotificationType notification_type, + const CheckResult::Ptr& cr, const String& author, const String& comment_text, const String& command_name); + void StateChangeHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); + void StateChangeHandlerInternal(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); String ComposeGelfMessage(const Dictionary::Ptr& fields, const String& source, double ts); void SendLogMessage(const Checkable::Ptr& checkable, const String& gelfMessage); From 060d8b185e318bbe9544132b8111c6e559a9cd7a Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 14 May 2025 12:24:28 +0200 Subject: [PATCH 292/415] Introduce AsioDualEvent --- lib/base/io-engine.cpp | 27 +++++++++++++++++++++++++++ lib/base/io-engine.hpp | 20 ++++++++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/lib/base/io-engine.cpp b/lib/base/io-engine.cpp index 30e2512d4..0792be5cc 100644 --- a/lib/base/io-engine.cpp +++ b/lib/base/io-engine.cpp @@ -146,6 +146,33 @@ void AsioEvent::Wait(boost::asio::yield_context yc) m_Timer.async_wait(yc[ec]); } +AsioDualEvent::AsioDualEvent(boost::asio::io_context& io, bool init) + : m_IsTrue(io, init), m_IsFalse(io, !init) +{ +} + +void AsioDualEvent::Set() +{ + m_IsTrue.Set(); + m_IsFalse.Clear(); +} + +void AsioDualEvent::Clear() +{ + m_IsTrue.Clear(); + m_IsFalse.Set(); +} + +void AsioDualEvent::WaitForSet(boost::asio::yield_context yc) +{ + m_IsTrue.Wait(std::move(yc)); +} + +void AsioDualEvent::WaitForClear(boost::asio::yield_context yc) +{ + m_IsFalse.Wait(std::move(yc)); +} + /** * Cancels any pending timeout callback. * diff --git a/lib/base/io-engine.hpp b/lib/base/io-engine.hpp index 049523e52..0883d7810 100644 --- a/lib/base/io-engine.hpp +++ b/lib/base/io-engine.hpp @@ -175,6 +175,26 @@ private: boost::asio::deadline_timer m_Timer; }; +/** + * Like AsioEvent, which only allows waiting for an event to be set, but additionally supports waiting for clearing + * + * @ingroup base + */ +class AsioDualEvent +{ +public: + AsioDualEvent(boost::asio::io_context& io, bool init = false); + + void Set(); + void Clear(); + + void WaitForSet(boost::asio::yield_context yc); + void WaitForClear(boost::asio::yield_context yc); + +private: + AsioEvent m_IsTrue, m_IsFalse; +}; + /** * I/O timeout emulator * From 2739f7f18954c5d60a7c42e055b98e6d4b77dae5 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 14 May 2025 12:28:11 +0200 Subject: [PATCH 293/415] RedisConnection#Connect(): get rid of spin lock Instead of IoEngine::YieldCurrentCoroutine(yc) until m_Queues.FutureResponseActions.empty(), async-wait a CV which is updated along with m_Queues.FutureResponseActions. --- lib/icingadb/redisconnection.cpp | 14 ++++---------- lib/icingadb/redisconnection.hpp | 3 ++- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/lib/icingadb/redisconnection.cpp b/lib/icingadb/redisconnection.cpp index c1f73f5a0..e0b026239 100644 --- a/lib/icingadb/redisconnection.cpp +++ b/lib/icingadb/redisconnection.cpp @@ -302,12 +302,6 @@ void RedisConnection::Connect(asio::yield_context& yc) boost::asio::deadline_timer timer (m_Strand.context()); - auto waitForReadLoop ([this, &yc]() { - while (!m_Queues.FutureResponseActions.empty()) { - IoEngine::YieldCurrentCoroutine(yc); - } - }); - for (;;) { try { if (m_Path.IsEmpty()) { @@ -339,7 +333,7 @@ void RedisConnection::Connect(asio::yield_context& yc) } Handshake(conn, yc); - waitForReadLoop(); + m_QueuedReads.WaitForClear(yc); m_TlsConn = std::move(conn); } else { Log(m_Parent ? LogNotice : LogInformation, "IcingaDB") @@ -350,7 +344,7 @@ void RedisConnection::Connect(asio::yield_context& yc) icinga::Connect(conn->next_layer(), m_Host, Convert::ToString(m_Port), yc); Handshake(conn, yc); - waitForReadLoop(); + m_QueuedReads.WaitForClear(yc); m_TcpConn = std::move(conn); } } else { @@ -362,7 +356,7 @@ void RedisConnection::Connect(asio::yield_context& yc) conn->next_layer().async_connect(Unix::endpoint(m_Path.CStr()), yc); Handshake(conn, yc); - waitForReadLoop(); + m_QueuedReads.WaitForClear(yc); m_UnixConn = std::move(conn); } @@ -394,7 +388,7 @@ void RedisConnection::Connect(asio::yield_context& yc) void RedisConnection::ReadLoop(asio::yield_context& yc) { for (;;) { - m_QueuedReads.Wait(yc); + m_QueuedReads.WaitForSet(yc); while (!m_Queues.FutureResponseActions.empty()) { auto item (std::move(m_Queues.FutureResponseActions.front())); diff --git a/lib/icingadb/redisconnection.hpp b/lib/icingadb/redisconnection.hpp index 2038f797c..308cf36d4 100644 --- a/lib/icingadb/redisconnection.hpp +++ b/lib/icingadb/redisconnection.hpp @@ -262,7 +262,8 @@ namespace icinga std::set m_SuppressedQueryKinds; // Indicate that there's something to send/receive - AsioEvent m_QueuedWrites, m_QueuedReads; + AsioEvent m_QueuedWrites; + AsioDualEvent m_QueuedReads; std::function m_ConnectedCallback; From daeab0933471e69cea68f624fd917faa85ca6058 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 14 May 2025 12:44:48 +0200 Subject: [PATCH 294/415] If skipped due to time period, schedule next check on next transition and not after yet another check interval. Otherwise checks done every 24h may get suppressed due to being re-scheduled outside time period every 24h. --- lib/checker/checkercomponent.cpp | 35 ++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/lib/checker/checkercomponent.cpp b/lib/checker/checkercomponent.cpp index 06ebb7bba..942a6fa9e 100644 --- a/lib/checker/checkercomponent.cpp +++ b/lib/checker/checkercomponent.cpp @@ -135,6 +135,7 @@ void CheckerComponent::CheckThreadProc() bool forced = checkable->GetForceNextCheck(); bool check = true; bool notifyNextCheck = false; + double nextCheck = -1; if (!forced) { if (!checkable->IsReachable(DependencyCheckExecution)) { @@ -162,13 +163,25 @@ void CheckerComponent::CheckThreadProc() TimePeriod::Ptr tp = checkable->GetCheckPeriod(); - if (tp && !tp->IsInside(Utility::GetTime())) { - Log(LogNotice, "CheckerComponent") - << "Skipping check for object '" << checkable->GetName() - << "': not in check period '" << tp->GetName() << "'"; + if (tp) { + auto ts (Utility::GetTime()); + ObjectLock oLock (tp); - check = false; - notifyNextCheck = true; + if (!tp->IsInside(ts)) { + nextCheck = tp->FindNextTransition(ts); + + if (nextCheck <= 0) { + nextCheck = tp->GetValidEnd(); + } + + Log(LogNotice, "CheckerComponent") + << "Skipping check for object '" << checkable->GetName() + << "', as not in check period '" << tp->GetName() << "', until " + << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", nextCheck); + + check = false; + notifyNextCheck = true; + } } } @@ -177,10 +190,14 @@ void CheckerComponent::CheckThreadProc() m_IdleCheckables.insert(GetCheckableScheduleInfo(checkable)); lock.unlock(); - Log(LogDebug, "CheckerComponent") - << "Checks for checkable '" << checkable->GetName() << "' are disabled. Rescheduling check."; + if (nextCheck > 0) { + checkable->SetNextCheck(nextCheck); + } else { + Log(LogDebug, "CheckerComponent") + << "Checks for checkable '" << checkable->GetName() << "' are disabled. Rescheduling check."; - checkable->UpdateNextCheck(); + checkable->UpdateNextCheck(); + } if (notifyNextCheck) { // Trigger update event for Icinga DB From 5a464afdaa2e250341468b497f3a4cf36597c9a5 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Wed, 14 May 2025 09:56:57 +0200 Subject: [PATCH 295/415] GitHub Actions: Show log files in Windows jobs If CPack fails, it may write the actual errors to a dedicated log file: EXEC : CPack error : Problem running WiX. Please check 'D:/a/icinga2/icinga2/Build/_CPack_Packages/win64/WIX/wix.log' for errors. [D:\a\icinga2\icinga2\Build\PACKAGE.vcxproj] Show all `*.log` files as part of the job output so that it doesn't get lost. --- .github/workflows/windows.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 5d682be3d..b04deaaab 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -46,3 +46,12 @@ jobs: if ($LastExitCode -ne 0) { throw "Error during build" } & powershell.exe .\tools\win32\test.ps1 if ($LastExitCode -ne 0) { throw "Error during test" } + + - name: Show Log Files + if: ${{ always() }} + run: | + foreach ($file in Get-ChildItem -Recurse -Filter "*.log") { + Write-Host "::group::$($file.FullName)" + Get-Content $file.FullName + Write-Host "::endgroup::" + } From cef6fb77e559b1155ef87cb50df297e63e30228a Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 25 Apr 2025 12:17:19 +0200 Subject: [PATCH 296/415] Serialize fields before queueing the event to the workqueue --- lib/perfdata/elasticsearchwriter.cpp | 76 +++++------- lib/perfdata/elasticsearchwriter.hpp | 4 - lib/perfdata/gelfwriter.cpp | 160 ++++++++++++-------------- lib/perfdata/gelfwriter.hpp | 8 +- lib/perfdata/graphitewriter.cpp | 74 +++++------- lib/perfdata/graphitewriter.hpp | 3 +- lib/perfdata/influxdbcommonwriter.cpp | 118 +++++++++---------- lib/perfdata/influxdbcommonwriter.hpp | 1 - 8 files changed, 191 insertions(+), 253 deletions(-) diff --git a/lib/perfdata/elasticsearchwriter.cpp b/lib/perfdata/elasticsearchwriter.cpp index ed4381931..8e6848ab3 100644 --- a/lib/perfdata/elasticsearchwriter.cpp +++ b/lib/perfdata/elasticsearchwriter.cpp @@ -236,15 +236,6 @@ void ElasticsearchWriter::CheckResultHandler(const Checkable::Ptr& checkable, co if (IsPaused()) return; - m_WorkQueue.Enqueue([this, checkable, cr]() { InternalCheckResultHandler(checkable, cr); }); -} - -void ElasticsearchWriter::InternalCheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) -{ - AssertOnWorkQueue(); - - CONTEXT("Elasticwriter processing check result for '" << checkable->GetName() << "'"); - if (!IcingaApplication::GetInstance()->GetEnablePerfdata() || !checkable->GetEnablePerfdata()) return; @@ -274,10 +265,15 @@ void ElasticsearchWriter::InternalCheckResultHandler(const Checkable::Ptr& check fields->Set("reachable", checkable->IsReachable()); fields->Set("check_command", checkable->GetCheckCommand()->GetName()); - AddCheckResult(fields, checkable, cr); AddTemplateTags(fields, checkable, cr); - Enqueue(checkable, "checkresult", fields, cr->GetExecutionEnd()); + m_WorkQueue.Enqueue([this, checkable, cr, fields = std::move(fields)]() { + CONTEXT("Elasticwriter processing check result for '" << checkable->GetName() << "'"); + + AddCheckResult(fields, checkable, cr); + + Enqueue(checkable, "checkresult", fields, cr->GetExecutionEnd()); + }); } void ElasticsearchWriter::StateChangeHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) @@ -285,15 +281,6 @@ void ElasticsearchWriter::StateChangeHandler(const Checkable::Ptr& checkable, co if (IsPaused()) return; - m_WorkQueue.Enqueue([this, checkable, cr]() { StateChangeHandlerInternal(checkable, cr); }); -} - -void ElasticsearchWriter::StateChangeHandlerInternal(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) -{ - AssertOnWorkQueue(); - - CONTEXT("Elasticwriter processing state change '" << checkable->GetName() << "'"); - Host::Ptr host; Service::Ptr service; tie(host, service) = GetHostService(checkable); @@ -317,10 +304,15 @@ void ElasticsearchWriter::StateChangeHandlerInternal(const Checkable::Ptr& check fields->Set("check_command", checkable->GetCheckCommand()->GetName()); - AddCheckResult(fields, checkable, cr); AddTemplateTags(fields, checkable, cr); - Enqueue(checkable, "statechange", fields, cr->GetExecutionEnd()); + m_WorkQueue.Enqueue([this, checkable, cr, fields = std::move(fields)]() { + CONTEXT("Elasticwriter processing state change '" << checkable->GetName() << "'"); + + AddCheckResult(fields, checkable, cr); + + Enqueue(checkable, "statechange", fields, cr->GetExecutionEnd()); + }); } void ElasticsearchWriter::NotificationSentToAllUsersHandler(const Checkable::Ptr& checkable, const std::set& users, @@ -329,21 +321,6 @@ void ElasticsearchWriter::NotificationSentToAllUsersHandler(const Checkable::Ptr if (IsPaused()) return; - m_WorkQueue.Enqueue([this, checkable, users, type, cr, author, text]() { - NotificationSentToAllUsersHandlerInternal(checkable, users, type, cr, author, text); - }); -} - -void ElasticsearchWriter::NotificationSentToAllUsersHandlerInternal(const Checkable::Ptr& checkable, const std::set& users, - NotificationType type, const CheckResult::Ptr& cr, const String& author, const String& text) -{ - AssertOnWorkQueue(); - - CONTEXT("Elasticwriter processing notification to all users '" << checkable->GetName() << "'"); - - Log(LogDebug, "ElasticsearchWriter") - << "Processing notification for '" << checkable->GetName() << "'"; - Host::Ptr host; Service::Ptr service; tie(host, service) = GetHostService(checkable); @@ -377,21 +354,30 @@ void ElasticsearchWriter::NotificationSentToAllUsersHandlerInternal(const Checka fields->Set("text", text); fields->Set("check_command", checkable->GetCheckCommand()->GetName()); - double ts = Utility::GetTime(); - - if (cr) { - AddCheckResult(fields, checkable, cr); - ts = cr->GetExecutionEnd(); - } - AddTemplateTags(fields, checkable, cr); - Enqueue(checkable, "notification", fields, ts); + m_WorkQueue.Enqueue([this, checkable, cr, fields = std::move(fields)]() { + CONTEXT("Elasticwriter processing notification to all users '" << checkable->GetName() << "'"); + + Log(LogDebug, "ElasticsearchWriter") + << "Processing notification for '" << checkable->GetName() << "'"; + + double ts = Utility::GetTime(); + + if (cr) { + AddCheckResult(fields, checkable, cr); + ts = cr->GetExecutionEnd(); + } + + Enqueue(checkable, "notification", fields, ts); + }); } void ElasticsearchWriter::Enqueue(const Checkable::Ptr& checkable, const String& type, const Dictionary::Ptr& fields, double ts) { + AssertOnWorkQueue(); + /* Atomically buffer the data point. */ std::unique_lock lock(m_DataBufferMutex); diff --git a/lib/perfdata/elasticsearchwriter.hpp b/lib/perfdata/elasticsearchwriter.hpp index 1f5eb60bf..c92f02c23 100644 --- a/lib/perfdata/elasticsearchwriter.hpp +++ b/lib/perfdata/elasticsearchwriter.hpp @@ -43,13 +43,9 @@ private: void AddTemplateTags(const Dictionary::Ptr& fields, const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); void StateChangeHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); - void StateChangeHandlerInternal(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); void CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); - void InternalCheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); void NotificationSentToAllUsersHandler(const Checkable::Ptr& checkable, const std::set& users, NotificationType type, const CheckResult::Ptr& cr, const String& author, const String& text); - void NotificationSentToAllUsersHandlerInternal(const Checkable::Ptr& checkable, const std::set& users, - NotificationType type, const CheckResult::Ptr& cr, const String& author, const String& text); void Enqueue(const Checkable::Ptr& checkable, const String& type, const Dictionary::Ptr& fields, double ts); diff --git a/lib/perfdata/gelfwriter.cpp b/lib/perfdata/gelfwriter.cpp index 5f7ae059b..59561d39a 100644 --- a/lib/perfdata/gelfwriter.cpp +++ b/lib/perfdata/gelfwriter.cpp @@ -268,18 +268,6 @@ void GelfWriter::CheckResultHandler(const Checkable::Ptr& checkable, const Check if (IsPaused()) return; - m_WorkQueue.Enqueue([this, checkable, cr]() { CheckResultHandlerInternal(checkable, cr); }); -} - -void GelfWriter::CheckResultHandlerInternal(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) -{ - AssertOnWorkQueue(); - - CONTEXT("GELF Processing check result for '" << checkable->GetName() << "'"); - - Log(LogDebug, "GelfWriter") - << "Processing check result for '" << checkable->GetName() << "'"; - Host::Ptr host; Service::Ptr service; tie(host, service) = GetHostService(checkable); @@ -308,80 +296,72 @@ void GelfWriter::CheckResultHandlerInternal(const Checkable::Ptr& checkable, con CheckCommand::Ptr checkCommand = checkable->GetCheckCommand(); fields->Set("_check_command", checkCommand->GetName()); - fields->Set("_latency", cr->CalculateLatency()); - fields->Set("_execution_time", cr->CalculateExecutionTime()); - fields->Set("short_message", CompatUtility::GetCheckResultOutput(cr)); - fields->Set("full_message", cr->GetOutput()); - fields->Set("_check_source", cr->GetCheckSource()); + m_WorkQueue.Enqueue([this, checkable, cr, fields = std::move(fields)]() { + CONTEXT("GELF Processing check result for '" << checkable->GetName() << "'"); - if (GetEnableSendPerfdata()) { - Array::Ptr perfdata = cr->GetPerformanceData(); + Log(LogDebug, "GelfWriter") + << "Processing check result for '" << checkable->GetName() << "'"; - if (perfdata) { - ObjectLock olock(perfdata); - for (const Value& val : perfdata) { - PerfdataValue::Ptr pdv; + fields->Set("_latency", cr->CalculateLatency()); + fields->Set("_execution_time", cr->CalculateExecutionTime()); + fields->Set("short_message", CompatUtility::GetCheckResultOutput(cr)); + fields->Set("full_message", cr->GetOutput()); + fields->Set("_check_source", cr->GetCheckSource()); - if (val.IsObjectType()) - pdv = val; - else { - try { - pdv = PerfdataValue::Parse(val); - } catch (const std::exception&) { - Log(LogWarning, "GelfWriter") - << "Ignoring invalid perfdata for checkable '" - << checkable->GetName() << "' and command '" - << checkCommand->GetName() << "' with value: " << val; - continue; + if (GetEnableSendPerfdata()) { + Array::Ptr perfdata = cr->GetPerformanceData(); + + if (perfdata) { + ObjectLock olock(perfdata); + for (const Value& val : perfdata) { + PerfdataValue::Ptr pdv; + + if (val.IsObjectType()) + pdv = val; + else { + try { + pdv = PerfdataValue::Parse(val); + } catch (const std::exception&) { + Log(LogWarning, "GelfWriter") + << "Ignoring invalid perfdata for checkable '" + << checkable->GetName() << "' and command '" + << checkable->GetCheckCommand()->GetName() << "' with value: " << val; + continue; + } } + + String escaped_key = pdv->GetLabel(); + boost::replace_all(escaped_key, " ", "_"); + boost::replace_all(escaped_key, ".", "_"); + boost::replace_all(escaped_key, "\\", "_"); + boost::algorithm::replace_all(escaped_key, "::", "."); + + fields->Set("_" + escaped_key, pdv->GetValue()); + + if (!pdv->GetMin().IsEmpty()) + fields->Set("_" + escaped_key + "_min", pdv->GetMin()); + if (!pdv->GetMax().IsEmpty()) + fields->Set("_" + escaped_key + "_max", pdv->GetMax()); + if (!pdv->GetWarn().IsEmpty()) + fields->Set("_" + escaped_key + "_warn", pdv->GetWarn()); + if (!pdv->GetCrit().IsEmpty()) + fields->Set("_" + escaped_key + "_crit", pdv->GetCrit()); + + if (!pdv->GetUnit().IsEmpty()) + fields->Set("_" + escaped_key + "_unit", pdv->GetUnit()); } - - String escaped_key = pdv->GetLabel(); - boost::replace_all(escaped_key, " ", "_"); - boost::replace_all(escaped_key, ".", "_"); - boost::replace_all(escaped_key, "\\", "_"); - boost::algorithm::replace_all(escaped_key, "::", "."); - - fields->Set("_" + escaped_key, pdv->GetValue()); - - if (!pdv->GetMin().IsEmpty()) - fields->Set("_" + escaped_key + "_min", pdv->GetMin()); - if (!pdv->GetMax().IsEmpty()) - fields->Set("_" + escaped_key + "_max", pdv->GetMax()); - if (!pdv->GetWarn().IsEmpty()) - fields->Set("_" + escaped_key + "_warn", pdv->GetWarn()); - if (!pdv->GetCrit().IsEmpty()) - fields->Set("_" + escaped_key + "_crit", pdv->GetCrit()); - - if (!pdv->GetUnit().IsEmpty()) - fields->Set("_" + escaped_key + "_unit", pdv->GetUnit()); } } - } - SendLogMessage(checkable, ComposeGelfMessage(fields, GetSource(), cr->GetExecutionEnd())); -} - -void GelfWriter::NotificationToUserHandler(const Checkable::Ptr& checkable, NotificationType notificationType, - CheckResult::Ptr const& cr, const String& author, const String& commentText, const String& commandName) -{ - if (IsPaused()) - return; - - m_WorkQueue.Enqueue([this, checkable, notificationType, cr, author, commentText, commandName]() { - NotificationToUserHandlerInternal(checkable, notificationType, cr, author, commentText, commandName); + SendLogMessage(checkable, ComposeGelfMessage(fields, GetSource(), cr->GetExecutionEnd())); }); } -void GelfWriter::NotificationToUserHandlerInternal(const Checkable::Ptr& checkable, NotificationType notificationType, - CheckResult::Ptr const& cr, const String& author, const String& commentText, const String& commandName) +void GelfWriter::NotificationToUserHandler(const Checkable::Ptr& checkable, NotificationType notificationType, + const CheckResult::Ptr& cr, const String& author, const String& commentText, const String& commandName) { - AssertOnWorkQueue(); - - CONTEXT("GELF Processing notification to all users '" << checkable->GetName() << "'"); - - Log(LogDebug, "GelfWriter") - << "Processing notification for '" << checkable->GetName() << "'"; + if (IsPaused()) + return; Host::Ptr host; Service::Ptr service; @@ -423,7 +403,14 @@ void GelfWriter::NotificationToUserHandlerInternal(const Checkable::Ptr& checkab fields->Set("_comment", authorComment); fields->Set("_check_command", checkable->GetCheckCommand()->GetName()); - SendLogMessage(checkable, ComposeGelfMessage(fields, GetSource(), ts)); + m_WorkQueue.Enqueue([this, checkable, ts, fields = std::move(fields)]() { + CONTEXT("GELF Processing notification to all users '" << checkable->GetName() << "'"); + + Log(LogDebug, "GelfWriter") + << "Processing notification for '" << checkable->GetName() << "'"; + + SendLogMessage(checkable, ComposeGelfMessage(fields, GetSource(), ts)); + }); } void GelfWriter::StateChangeHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) @@ -431,18 +418,6 @@ void GelfWriter::StateChangeHandler(const Checkable::Ptr& checkable, const Check if (IsPaused()) return; - m_WorkQueue.Enqueue([this, checkable, cr]() { StateChangeHandlerInternal(checkable, cr); }); -} - -void GelfWriter::StateChangeHandlerInternal(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) -{ - AssertOnWorkQueue(); - - CONTEXT("GELF Processing state change '" << checkable->GetName() << "'"); - - Log(LogDebug, "GelfWriter") - << "Processing state change for '" << checkable->GetName() << "'"; - Host::Ptr host; Service::Ptr service; tie(host, service) = GetHostService(checkable); @@ -470,7 +445,14 @@ void GelfWriter::StateChangeHandlerInternal(const Checkable::Ptr& checkable, con fields->Set("full_message", cr->GetOutput()); fields->Set("_check_source", cr->GetCheckSource()); - SendLogMessage(checkable, ComposeGelfMessage(fields, GetSource(), cr->GetExecutionEnd())); + m_WorkQueue.Enqueue([this, checkable, fields = std::move(fields), ts = cr->GetExecutionEnd()]() { + CONTEXT("GELF Processing state change '" << checkable->GetName() << "'"); + + Log(LogDebug, "GelfWriter") + << "Processing state change for '" << checkable->GetName() << "'"; + + SendLogMessage(checkable, ComposeGelfMessage(fields, GetSource(), ts)); + }); } String GelfWriter::ComposeGelfMessage(const Dictionary::Ptr& fields, const String& source, double ts) @@ -484,6 +466,8 @@ String GelfWriter::ComposeGelfMessage(const Dictionary::Ptr& fields, const Strin void GelfWriter::SendLogMessage(const Checkable::Ptr& checkable, const String& gelfMessage) { + AssertOnWorkQueue(); + std::ostringstream msgbuf; msgbuf << gelfMessage; msgbuf << '\0'; diff --git a/lib/perfdata/gelfwriter.hpp b/lib/perfdata/gelfwriter.hpp index b1dc90796..9d6b336a7 100644 --- a/lib/perfdata/gelfwriter.hpp +++ b/lib/perfdata/gelfwriter.hpp @@ -40,13 +40,9 @@ private: Timer::Ptr m_ReconnectTimer; void CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); - void CheckResultHandlerInternal(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); - void NotificationToUserHandler(const Checkable::Ptr& checkable, NotificationType notificationType, - const CheckResult::Ptr& cr, const String& author, const String& commentText, const String& commandName); - void NotificationToUserHandlerInternal(const Checkable::Ptr& checkable, NotificationType notification_type, - const CheckResult::Ptr& cr, const String& author, const String& comment_text, const String& command_name); + void NotificationToUserHandler(const Checkable::Ptr& checkable, NotificationType notificationType, const CheckResult::Ptr& cr, + const String& author, const String& commentText, const String& commandName); void StateChangeHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); - void StateChangeHandlerInternal(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); String ComposeGelfMessage(const Dictionary::Ptr& fields, const String& source, double ts); void SendLogMessage(const Checkable::Ptr& checkable, const String& gelfMessage); diff --git a/lib/perfdata/graphitewriter.cpp b/lib/perfdata/graphitewriter.cpp index 6adae0233..2adbb64f3 100644 --- a/lib/perfdata/graphitewriter.cpp +++ b/lib/perfdata/graphitewriter.cpp @@ -261,27 +261,6 @@ void GraphiteWriter::CheckResultHandler(const Checkable::Ptr& checkable, const C if (IsPaused()) return; - m_WorkQueue.Enqueue([this, checkable, cr]() { CheckResultHandlerInternal(checkable, cr); }); -} - -/** - * Check result event handler, prepares metadata and perfdata values and calls Send*() - * - * Called inside the WQ. - * - * @param checkable Host/Service object - * @param cr Check result including performance data - */ -void GraphiteWriter::CheckResultHandlerInternal(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) -{ - AssertOnWorkQueue(); - - CONTEXT("Processing check result for '" << checkable->GetName() << "'"); - - /* TODO: Deal with missing connection here. Needs refactoring - * into parsing the actual performance data and then putting it - * into a queue for re-inserting. */ - if (!IcingaApplication::GetInstance()->GetEnablePerfdata() || !checkable->GetEnablePerfdata()) return; @@ -306,29 +285,34 @@ void GraphiteWriter::CheckResultHandlerInternal(const Checkable::Ptr& checkable, }); } - String prefixPerfdata = prefix + ".perfdata"; - String prefixMetadata = prefix + ".metadata"; - - double ts = cr->GetExecutionEnd(); - + std::vector> metadata; if (GetEnableSendMetadata()) { - if (service) { - SendMetric(checkable, prefixMetadata, "state", service->GetState(), ts); - } else { - SendMetric(checkable, prefixMetadata, "state", host->GetState(), ts); - } - - SendMetric(checkable, prefixMetadata, "current_attempt", checkable->GetCheckAttempt(), ts); - SendMetric(checkable, prefixMetadata, "max_check_attempts", checkable->GetMaxCheckAttempts(), ts); - SendMetric(checkable, prefixMetadata, "state_type", checkable->GetStateType(), ts); - SendMetric(checkable, prefixMetadata, "reachable", checkable->IsReachable(), ts); - SendMetric(checkable, prefixMetadata, "downtime_depth", checkable->GetDowntimeDepth(), ts); - SendMetric(checkable, prefixMetadata, "acknowledgement", checkable->GetAcknowledgement(), ts); - SendMetric(checkable, prefixMetadata, "latency", cr->CalculateLatency(), ts); - SendMetric(checkable, prefixMetadata, "execution_time", cr->CalculateExecutionTime(), ts); + metadata = { + {"state", service ? service->GetState() : host->GetState()}, + {"current_attempt", checkable->GetCheckAttempt()}, + {"max_check_attempts", checkable->GetMaxCheckAttempts()}, + {"state_type", checkable->GetStateType()}, + {"reachable", checkable->IsReachable()}, + {"downtime_depth", checkable->GetDowntimeDepth()}, + {"acknowledgement", checkable->GetAcknowledgement()}, + {"latency", cr->CalculateLatency()}, + {"execution_time", cr->CalculateExecutionTime()} + }; } - SendPerfdata(checkable, prefixPerfdata, cr, ts); + m_WorkQueue.Enqueue([this, checkable, cr, prefix = std::move(prefix), metadata = std::move(metadata)]() { + CONTEXT("Processing check result for '" << checkable->GetName() << "'"); + + /* TODO: Deal with missing connection here. Needs refactoring + * into parsing the actual performance data and then putting it + * into a queue for re-inserting. */ + + for (auto& [name, val] : metadata) { + SendMetric(checkable, prefix + ".metadata", name, val, cr->GetExecutionEnd()); + } + + SendPerfdata(checkable, prefix + ".perfdata", cr); + }); } /** @@ -337,10 +321,11 @@ void GraphiteWriter::CheckResultHandlerInternal(const Checkable::Ptr& checkable, * @param checkable Host/service object * @param prefix Metric prefix string * @param cr Check result including performance data - * @param ts Timestamp when the check result was created */ -void GraphiteWriter::SendPerfdata(const Checkable::Ptr& checkable, const String& prefix, const CheckResult::Ptr& cr, double ts) +void GraphiteWriter::SendPerfdata(const Checkable::Ptr& checkable, const String& prefix, const CheckResult::Ptr& cr) { + AssertOnWorkQueue(); + Array::Ptr perfdata = cr->GetPerformanceData(); if (!perfdata) @@ -367,6 +352,7 @@ void GraphiteWriter::SendPerfdata(const Checkable::Ptr& checkable, const String& } String escapedKey = EscapeMetricLabel(pdv->GetLabel()); + double ts = cr->GetExecutionEnd(); SendMetric(checkable, prefix, escapedKey + ".value", pdv->GetValue(), ts); @@ -394,6 +380,8 @@ void GraphiteWriter::SendPerfdata(const Checkable::Ptr& checkable, const String& */ void GraphiteWriter::SendMetric(const Checkable::Ptr& checkable, const String& prefix, const String& name, double value, double ts) { + AssertOnWorkQueue(); + namespace asio = boost::asio; std::ostringstream msgbuf; diff --git a/lib/perfdata/graphitewriter.hpp b/lib/perfdata/graphitewriter.hpp index e0c8b7846..9aa0dc047 100644 --- a/lib/perfdata/graphitewriter.hpp +++ b/lib/perfdata/graphitewriter.hpp @@ -45,9 +45,8 @@ private: Timer::Ptr m_ReconnectTimer; void CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); - void CheckResultHandlerInternal(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); void SendMetric(const Checkable::Ptr& checkable, const String& prefix, const String& name, double value, double ts); - void SendPerfdata(const Checkable::Ptr& checkable, const String& prefix, const CheckResult::Ptr& cr, double ts); + void SendPerfdata(const Checkable::Ptr& checkable, const String& prefix, const CheckResult::Ptr& cr); static String EscapeMetric(const String& str); static String EscapeMetricLabel(const String& str); static Value EscapeMacroMetric(const Value& value); diff --git a/lib/perfdata/influxdbcommonwriter.cpp b/lib/perfdata/influxdbcommonwriter.cpp index d5aaa7c98..14c5b004f 100644 --- a/lib/perfdata/influxdbcommonwriter.cpp +++ b/lib/perfdata/influxdbcommonwriter.cpp @@ -204,15 +204,6 @@ void InfluxdbCommonWriter::CheckResultHandler(const Checkable::Ptr& checkable, c if (IsPaused()) return; - m_WorkQueue.Enqueue([this, checkable, cr]() { CheckResultHandlerWQ(checkable, cr); }, PriorityLow); -} - -void InfluxdbCommonWriter::CheckResultHandlerWQ(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr) -{ - AssertOnWorkQueue(); - - CONTEXT("Processing check result for '" << checkable->GetName() << "'"); - if (!IcingaApplication::GetInstance()->GetEnablePerfdata() || !checkable->GetEnablePerfdata()) return; @@ -225,10 +216,6 @@ void InfluxdbCommonWriter::CheckResultHandlerWQ(const Checkable::Ptr& checkable, resolvers.emplace_back("service", service); resolvers.emplace_back("host", host); - String prefix; - - double ts = cr->GetExecutionEnd(); - // Clone the template and perform an in-place macro expansion of measurement and tag values Dictionary::Ptr tmpl_clean = service ? GetServiceTemplate() : GetHostTemplate(); Dictionary::Ptr tmpl = static_pointer_cast(tmpl_clean->ShallowClone()); @@ -253,56 +240,9 @@ void InfluxdbCommonWriter::CheckResultHandlerWQ(const Checkable::Ptr& checkable, tmpl->Set("tags", tags); } - CheckCommand::Ptr checkCommand = checkable->GetCheckCommand(); - - Array::Ptr perfdata = cr->GetPerformanceData(); - - if (perfdata) { - ObjectLock olock(perfdata); - for (const Value& val : perfdata) { - PerfdataValue::Ptr pdv; - - if (val.IsObjectType()) - pdv = val; - else { - try { - pdv = PerfdataValue::Parse(val); - } catch (const std::exception&) { - Log(LogWarning, GetReflectionType()->GetName()) - << "Ignoring invalid perfdata for checkable '" - << checkable->GetName() << "' and command '" - << checkCommand->GetName() << "' with value: " << val; - continue; - } - } - - Dictionary::Ptr fields = new Dictionary(); - fields->Set("value", pdv->GetValue()); - - if (GetEnableSendThresholds()) { - if (!pdv->GetCrit().IsEmpty()) - fields->Set("crit", pdv->GetCrit()); - if (!pdv->GetWarn().IsEmpty()) - fields->Set("warn", pdv->GetWarn()); - if (!pdv->GetMin().IsEmpty()) - fields->Set("min", pdv->GetMin()); - if (!pdv->GetMax().IsEmpty()) - fields->Set("max", pdv->GetMax()); - } - if (!pdv->GetUnit().IsEmpty()) { - fields->Set("unit", pdv->GetUnit()); - } - - SendMetric(checkable, tmpl, pdv->GetLabel(), fields, ts); - } - } - + Dictionary::Ptr fields; if (GetEnableSendMetadata()) { - Host::Ptr host; - Service::Ptr service; - tie(host, service) = GetHostService(checkable); - - Dictionary::Ptr fields = new Dictionary(); + fields = new Dictionary(); if (service) fields->Set("state", new InfluxdbInteger(service->GetState())); @@ -317,9 +257,57 @@ void InfluxdbCommonWriter::CheckResultHandlerWQ(const Checkable::Ptr& checkable, fields->Set("acknowledgement", new InfluxdbInteger(checkable->GetAcknowledgement())); fields->Set("latency", cr->CalculateLatency()); fields->Set("execution_time", cr->CalculateExecutionTime()); - - SendMetric(checkable, tmpl, Empty, fields, ts); } + + m_WorkQueue.Enqueue([this, checkable, cr, tmpl = std::move(tmpl), metadataFields = std::move(fields)]() { + CONTEXT("Processing check result for '" << checkable->GetName() << "'"); + + double ts = cr->GetExecutionEnd(); + + if (Array::Ptr perfdata = cr->GetPerformanceData()) { + ObjectLock olock(perfdata); + for (const Value& val : perfdata) { + PerfdataValue::Ptr pdv; + + if (val.IsObjectType()) + pdv = val; + else { + try { + pdv = PerfdataValue::Parse(val); + } catch (const std::exception&) { + Log(LogWarning, GetReflectionType()->GetName()) + << "Ignoring invalid perfdata for checkable '" + << checkable->GetName() << "' and command '" + << checkable->GetCheckCommand()->GetName() << "' with value: " << val; + continue; + } + } + + Dictionary::Ptr fields = new Dictionary(); + fields->Set("value", pdv->GetValue()); + + if (GetEnableSendThresholds()) { + if (!pdv->GetCrit().IsEmpty()) + fields->Set("crit", pdv->GetCrit()); + if (!pdv->GetWarn().IsEmpty()) + fields->Set("warn", pdv->GetWarn()); + if (!pdv->GetMin().IsEmpty()) + fields->Set("min", pdv->GetMin()); + if (!pdv->GetMax().IsEmpty()) + fields->Set("max", pdv->GetMax()); + } + if (!pdv->GetUnit().IsEmpty()) { + fields->Set("unit", pdv->GetUnit()); + } + + SendMetric(checkable, tmpl, pdv->GetLabel(), fields, ts); + } + } + + if (metadataFields) { + SendMetric(checkable, tmpl, Empty, metadataFields, ts); + } + }, PriorityLow); } String InfluxdbCommonWriter::EscapeKeyOrTagValue(const String& str) @@ -364,6 +352,8 @@ String InfluxdbCommonWriter::EscapeValue(const Value& value) void InfluxdbCommonWriter::SendMetric(const Checkable::Ptr& checkable, const Dictionary::Ptr& tmpl, const String& label, const Dictionary::Ptr& fields, double ts) { + AssertOnWorkQueue(); + std::ostringstream msgbuf; msgbuf << EscapeKeyOrTagValue(tmpl->Get("measurement")); diff --git a/lib/perfdata/influxdbcommonwriter.hpp b/lib/perfdata/influxdbcommonwriter.hpp index 380b20c9f..9d3427f7e 100644 --- a/lib/perfdata/influxdbcommonwriter.hpp +++ b/lib/perfdata/influxdbcommonwriter.hpp @@ -54,7 +54,6 @@ private: std::atomic_size_t m_DataBufferSize{0}; void CheckResultHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); - void CheckResultHandlerWQ(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr); void SendMetric(const Checkable::Ptr& checkable, const Dictionary::Ptr& tmpl, const String& label, const Dictionary::Ptr& fields, double ts); void FlushTimeout(); From 2a79e1695db48e0ae37f35410398831cf0ba8012 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Fri, 16 May 2025 09:13:07 +0200 Subject: [PATCH 297/415] AUTHORS, .mailmap: Add Dirk Wening --- .mailmap | 1 + AUTHORS | 1 + 2 files changed, 2 insertions(+) diff --git a/.mailmap b/.mailmap index df55f3a0f..36496bc29 100644 --- a/.mailmap +++ b/.mailmap @@ -42,6 +42,7 @@ Diana Flach Diana Flach Diana Flach Diana Flach Jean Flach +Dirk Wening <170401214+SpeedD3@users.noreply.github.com> Dolf Schimmel Gunnar Beutner Henrik Triem diff --git a/AUTHORS b/AUTHORS index dcd471c6b..0718440ad 100644 --- a/AUTHORS +++ b/AUTHORS @@ -77,6 +77,7 @@ Diana Flach Didier 'OdyX' Raboud Dinesh Majrekar Dirk Goetz +Dirk Wening Dirk Melchers Dolf Schimmel Dominik Riva From 5ea666a7ad7839963bcd2a6aba0c441cfebe21e9 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 14 Mar 2025 11:20:44 +0100 Subject: [PATCH 298/415] IcingaDB: Don't set `cancel_time` for downtime start event It's a downtime start event there's now way the downtime could be cancelled before it even started. --- lib/icingadb/icingadb-objects.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index 40580a358..29f946708 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -2131,7 +2131,6 @@ void IcingaDB::SendStartedDowntime(const Downtime::Ptr& downtime) "scheduled_end_time", Convert::ToString(TimestampToMilliseconds(downtime->GetEndTime())), "has_been_cancelled", Convert::ToString((unsigned short)downtime->GetWasCancelled()), "trigger_time", Convert::ToString(TimestampToMilliseconds(downtime->GetTriggerTime())), - "cancel_time", Convert::ToString(TimestampToMilliseconds(downtime->GetRemoveTime())), "event_id", CalcEventID("downtime_start", downtime), "event_type", "downtime_start" }); From 7acec6fc36a878bae1806bd8964d34170be1a4c3 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 14 Mar 2025 11:36:37 +0100 Subject: [PATCH 299/415] IcingaDB: Set downtime `cancel_time` conditionally If the downtime ended automatically `cancel_time` should just be `NULL` instead of a `0` timestamp. --- lib/icingadb/icingadb-objects.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index 29f946708..914d46bc0 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -2221,7 +2221,6 @@ void IcingaDB::SendRemovedDowntime(const Downtime::Ptr& downtime) "scheduled_end_time", Convert::ToString(TimestampToMilliseconds(downtime->GetEndTime())), "has_been_cancelled", Convert::ToString((unsigned short)downtime->GetWasCancelled()), "trigger_time", Convert::ToString(TimestampToMilliseconds(downtime->GetTriggerTime())), - "cancel_time", Convert::ToString(TimestampToMilliseconds(downtime->GetRemoveTime())), "event_id", CalcEventID("downtime_end", downtime), "event_type", "downtime_end" }); @@ -2241,6 +2240,11 @@ void IcingaDB::SendRemovedDowntime(const Downtime::Ptr& downtime) xAdd.emplace_back(GetObjectIdentifier(triggeredBy)); } + if (downtime->GetWasCancelled()) { + xAdd.emplace_back("cancel_time"); + xAdd.emplace_back(Convert::ToString(TimestampToMilliseconds(downtime->GetRemoveTime()))); + } + if (downtime->GetFixed()) { xAdd.emplace_back("start_time"); xAdd.emplace_back(Convert::ToString(TimestampToMilliseconds(downtime->GetStartTime()))); From 018a0c936d1d0cebc881cd3545814ed2e423a017 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Fri, 16 May 2025 10:54:24 +0200 Subject: [PATCH 300/415] GHA: AUTHORS: use sed(1), not grep(1) grep(1)'s exit code causes the GHA to fail in contrast to sed(1). --- .github/workflows/authors-file.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/authors-file.yml b/.github/workflows/authors-file.yml index 2e5ac25ee..8824bbdab 100644 --- a/.github/workflows/authors-file.yml +++ b/.github/workflows/authors-file.yml @@ -21,7 +21,7 @@ jobs: git add AUTHORS git log --format='format:%aN <%aE>' "$( git merge-base HEAD^1 HEAD^2 - )..HEAD^2" | grep -vEe '^dependabot\[bot] ' >> AUTHORS + )..HEAD^2" | sed '/^dependabot\[bot] /d' >> AUTHORS sort -uo AUTHORS AUTHORS git diff AUTHORS >> AUTHORS.diff From 908519aa74658693bf67c62a43285fb51fd783be Mon Sep 17 00:00:00 2001 From: Dirk Wening <170401214+SpeedD3@users.noreply.github.com> Date: Wed, 14 May 2025 13:42:45 +0200 Subject: [PATCH 301/415] ITL: Add argument maintenance_mode_state to vmware_esx --- doc/10-icinga-template-library.md | 2827 +++++++++++++++-------------- itl/plugins-contrib.d/vmware.conf | 4 + 2 files changed, 1455 insertions(+), 1376 deletions(-) diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md index 6ae7a926f..45bfa7b53 100644 --- a/doc/10-icinga-template-library.md +++ b/doc/10-icinga-template-library.md @@ -3929,31 +3929,32 @@ Check command object for the `check_vmware_esx` plugin. Shows all datastore volu Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Required.** Datacenter/vCenter hostname. -vmware_cluster | **Optional.** ESX or ESXi clustername. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_subselect | **Optional.** Volume name to be checked the free space. -vmware_gigabyte | **Optional.** Output in GB instead of MB. -vmware_usedspace | **Optional.** Output used space instead of free. Defaults to "false". -vmware_alertonly | **Optional.** List only alerting volumes. Defaults to "false". -vmware_exclude | **Optional.** Blacklist volumes name. No value defined as default. -vmware_include | **Optional.** Whitelist volumes name. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. -vmware_dc_volume_used | **Optional.** Output used space instead of free. Defaults to "true". -vmware_warn | **Optional.** The warning threshold for volumes. Defaults to "80%". -vmware_crit | **Optional.** The critical threshold for volumes. Defaults to "90%". +Name | Description +------------------------------|-------------- +vmware_datacenter | **Required.** Datacenter/vCenter hostname. +vmware_cluster | **Optional.** ESX or ESXi clustername. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_subselect | **Optional.** Volume name to be checked the free space. +vmware_gigabyte | **Optional.** Output in GB instead of MB. +vmware_usedspace | **Optional.** Output used space instead of free. Defaults to "false". +vmware_alertonly | **Optional.** List only alerting volumes. Defaults to "false". +vmware_exclude | **Optional.** Blacklist volumes name. No value defined as default. +vmware_include | **Optional.** Whitelist volumes name. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +vmware_dc_volume_used | **Optional.** Output used space instead of free. Defaults to "true". +vmware_warn | **Optional.** The warning threshold for volumes. Defaults to "80%". +vmware_crit | **Optional.** The critical threshold for volumes. Defaults to "90%". +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-dc-runtime-info** @@ -3962,21 +3963,22 @@ Check command object for the `check_vmware_esx` plugin. Shows all runtime info f Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Required.** Datacenter/vCenter hostname. -vmware_cluster | **Optional.** ESX or ESXi clustername. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +Name | Description +------------------------------|-------------- +vmware_datacenter | **Required.** Datacenter/vCenter hostname. +vmware_cluster | **Optional.** ESX or ESXi clustername. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-dc-runtime-listvms** @@ -3985,26 +3987,27 @@ Check command object for the `check_vmware_esx` plugin. List of vmware machines Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Required.** Datacenter/vCenter hostname. -vmware_cluster | **Optional.** ESX or ESXi clustername. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_alertonly | **Optional.** List only alerting VMs. Important here to avoid masses of data. -vmware_exclude | **Optional.** Blacklist VMs name. No value defined as default. -vmware_include | **Optional.** Whitelist VMs name. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. -vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_datacenter | **Required.** Datacenter/vCenter hostname. +vmware_cluster | **Optional.** ESX or ESXi clustername. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_alertonly | **Optional.** List only alerting VMs. Important here to avoid masses of data. +vmware_exclude | **Optional.** Blacklist VMs name. No value defined as default. +vmware_include | **Optional.** Whitelist VMs name. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-dc-runtime-listhost** @@ -4013,26 +4016,27 @@ Check command object for the `check_vmware_esx` plugin. List of VMware ESX hosts Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Required.** Datacenter/vCenter hostname. -vmware_cluster | **Optional.** ESX or ESXi clustername. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_alertonly | **Optional.** List only alerting hosts. Important here to avoid masses of data. -vmware_exclude | **Optional.** Blacklist VMware ESX hosts. No value defined as default. -vmware_include | **Optional.** Whitelist VMware ESX hosts. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. -vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_datacenter | **Required.** Datacenter/vCenter hostname. +vmware_cluster | **Optional.** ESX or ESXi clustername. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_alertonly | **Optional.** List only alerting hosts. Important here to avoid masses of data. +vmware_exclude | **Optional.** Blacklist VMware ESX hosts. No value defined as default. +vmware_include | **Optional.** Whitelist VMware ESX hosts. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-dc-runtime-listcluster** @@ -4041,26 +4045,27 @@ Check command object for the `check_vmware_esx` plugin. List of VMware clusters Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Required.** Datacenter/vCenter hostname. -vmware_cluster | **Optional.** ESX or ESXi clustername. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_alertonly | **Optional.** List only alerting hosts. Important here to avoid masses of data. -vmware_exclude | **Optional.** Blacklist VMware cluster. No value defined as default. -vmware_include | **Optional.** Whitelist VMware cluster. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. -vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_datacenter | **Required.** Datacenter/vCenter hostname. +vmware_cluster | **Optional.** ESX or ESXi clustername. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_alertonly | **Optional.** List only alerting hosts. Important here to avoid masses of data. +vmware_exclude | **Optional.** Blacklist VMware cluster. No value defined as default. +vmware_include | **Optional.** Whitelist VMware cluster. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-dc-runtime-issues** @@ -4069,25 +4074,26 @@ Check command object for the `check_vmware_esx` plugin. All issues for the host. Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Required.** Datacenter/vCenter hostname. -vmware_cluster | **Optional.** ESX or ESXi clustername. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_exclude | **Optional.** Blacklist issues. No value defined as default. -vmware_include | **Optional.** Whitelist issues. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. -vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_datacenter | **Required.** Datacenter/vCenter hostname. +vmware_cluster | **Optional.** ESX or ESXi clustername. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_exclude | **Optional.** Blacklist issues. No value defined as default. +vmware_include | **Optional.** Whitelist issues. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-dc-runtime-status** @@ -4096,21 +4102,22 @@ Check command object for the `check_vmware_esx` plugin. Overall object status (g Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Required.** Datacenter/vCenter hostname. -vmware_cluster | **Optional.** ESX or ESXi clustername. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +Name | Description +------------------------------|-------------- +vmware_datacenter | **Required.** Datacenter/vCenter hostname. +vmware_cluster | **Optional.** ESX or ESXi clustername. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-dc-runtime-tools** @@ -4119,29 +4126,30 @@ Check command object for the `check_vmware_esx` plugin. Vmware Tools status. Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Required.** Datacenter/vCenter hostname. -vmware_cluster | **Optional.** ESX or ESXi clustername. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_poweredonly | **Optional.** List only VMs which are powered on. No value defined as default. -vmware_alertonly | **Optional.** List only alerting VMs. Important here to avoid masses of data. -vmware_exclude | **Optional.** Blacklist VMs. No value defined as default. -vmware_include | **Optional.** Whitelist VMs. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. -vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. -vmware_openvmtools | **Optional** Prevent CRITICAL state for installed and running Open VM Tools. -vmware_novmtools | **Optional** Prevent CRITICAL state for missing VMware tools. +Name | Description +------------------------------|-------------- +vmware_datacenter | **Required.** Datacenter/vCenter hostname. +vmware_cluster | **Optional.** ESX or ESXi clustername. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_poweredonly | **Optional.** List only VMs which are powered on. No value defined as default. +vmware_alertonly | **Optional.** List only alerting VMs. Important here to avoid masses of data. +vmware_exclude | **Optional.** Blacklist VMs. No value defined as default. +vmware_include | **Optional.** Whitelist VMs. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +vmware_openvmtools | **Optional** Prevent CRITICAL state for installed and running Open VM Tools. +vmware_novmtools | **Optional** Prevent CRITICAL state for missing VMware tools. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-check** @@ -4150,21 +4158,22 @@ Check command object for the `check_vmware_esx` plugin. Simple check to verify a Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-uptime** @@ -4173,21 +4182,22 @@ Check command object for the `check_vmware_esx` plugin. Displays uptime of the V Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-cpu** @@ -4196,23 +4206,24 @@ Check command object for the `check_vmware_esx` plugin. CPU usage in percentage. Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold in percent. Defaults to "80%". -vmware_crit | **Optional.** The critical threshold in percent. Defaults to "90%". +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold in percent. Defaults to "80%". +vmware_crit | **Optional.** The critical threshold in percent. Defaults to "90%". +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-cpu-ready** @@ -4221,21 +4232,22 @@ Check command object for the `check_vmware_esx` plugin. Percentage of time that Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-cpu-wait** @@ -4244,21 +4256,22 @@ Check command object for the `check_vmware_esx` plugin. CPU time spent in wait s Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-cpu-usage** @@ -4267,23 +4280,24 @@ Check command object for the `check_vmware_esx` plugin. Actively used CPU of the Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold in percent. Defaults to "80%". -vmware_crit | **Optional.** The critical threshold in percent. Defaults to "90%". +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold in percent. Defaults to "80%". +vmware_crit | **Optional.** The critical threshold in percent. Defaults to "90%". +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-mem** @@ -4292,21 +4306,22 @@ Check command object for the `check_vmware_esx` plugin. All mem info(except over Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-mem-usage** @@ -4315,23 +4330,24 @@ Check command object for the `check_vmware_esx` plugin. Average mem usage in per Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold in percent. Defaults to "80%". -vmware_crit | **Optional.** The critical threshold in percent. Defaults to "90%". +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold in percent. Defaults to "80%". +vmware_crit | **Optional.** The critical threshold in percent. Defaults to "90%". +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-mem-consumed** @@ -4340,23 +4356,24 @@ Check command object for the `check_vmware_esx` plugin. Amount of machine memory Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold in percent. No value defined as default. -vmware_crit | **Optional.** The critical threshold in percent. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold in percent. No value defined as default. +vmware_crit | **Optional.** The critical threshold in percent. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-mem-swapused** @@ -4365,24 +4382,25 @@ Check command object for the `check_vmware_esx` plugin. Amount of memory that is Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold in percent. No value defined as default. -vmware_crit | **Optional.** The critical threshold in percent. No value defined as default. -vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold in percent. No value defined as default. +vmware_crit | **Optional.** The critical threshold in percent. No value defined as default. +vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-mem-overhead** @@ -4391,23 +4409,24 @@ Check command object for the `check_vmware_esx` plugin. Additional mem used by V Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Auhentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold in percent. No value defined as default. -vmware_crit | **Optional.** The critical threshold in percent. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Auhentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold in percent. No value defined as default. +vmware_crit | **Optional.** The critical threshold in percent. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-mem-memctl** @@ -4416,24 +4435,25 @@ Check command object for the `check_vmware_esx` plugin. The sum of all vmmemctl Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold in percent. No value defined as default. -vmware_crit | **Optional.** The critical threshold in percent. No value defined as default. -vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold in percent. No value defined as default. +vmware_crit | **Optional.** The critical threshold in percent. No value defined as default. +vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-net** @@ -4442,24 +4462,25 @@ Check command object for the `check_vmware_esx` plugin. Shows net info. Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -----------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_exclude | **Optional.** Blacklist NICs. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist expression as regexp. -vmware_unplugged_nics_state | **Optional.** Sets status for unplugged nics (Possible values are: [OK | ok] or [CRITICAL | critical | CRIT | crit] or [WARNING | warning | WARN | warn]. Default is WARNING. Values are case insensitive.) +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_exclude | **Optional.** Blacklist NICs. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist expression as regexp. +vmware_unplugged_nics_state | **Optional.** Sets status for unplugged nics (Possible values are: [OK | ok] or [CRITICAL | critical | CRIT | crit] or [WARNING | warning | WARN | warn]. Default is WARNING. Values are case insensitive.) +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-net-usage** @@ -4468,23 +4489,24 @@ Check command object for the `check_vmware_esx` plugin. Overall network usage in Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold in KBps(Kilobytes per Second). No value defined as default. -vmware_crit | **Optional.** The critical threshold in KBps(Kilobytes per Second). No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold in KBps(Kilobytes per Second). No value defined as default. +vmware_crit | **Optional.** The critical threshold in KBps(Kilobytes per Second). No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-net-receive** @@ -4493,23 +4515,24 @@ Check command object for the `check_vmware_esx` plugin. Data receive in KBps(Kil Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold in KBps(Kilobytes per Second). No value defined as default. -vmware_crit | **Optional.** The critical threshold in KBps(Kilobytes per Second). No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold in KBps(Kilobytes per Second). No value defined as default. +vmware_crit | **Optional.** The critical threshold in KBps(Kilobytes per Second). No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-net-send** @@ -4518,23 +4541,24 @@ Check command object for the `check_vmware_esx` plugin. Data send in KBps(Kiloby Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold in KBps(Kilobytes per Second). No value defined as default. -vmware_crit | **Optional.** The critical threshold in KBps(Kilobytes per Second). No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold in KBps(Kilobytes per Second). No value defined as default. +vmware_crit | **Optional.** The critical threshold in KBps(Kilobytes per Second). No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-net-nic** @@ -4543,24 +4567,25 @@ Check command object for the `check_vmware_esx` plugin. Check all active NICs. Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -----------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_exclude | **Optional.** Blacklist NICs. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist expression as regexp. -vmware_unplugged_nics_state | **Optional.** Sets status for unplugged nics (Possible values are: [OK | ok] or [CRITICAL | critical | CRIT | crit] or [WARNING | warning | WARN | warn]. Default is WARNING. Values are case insensitive.) +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_exclude | **Optional.** Blacklist NICs. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist expression as regexp. +vmware_unplugged_nics_state | **Optional.** Sets status for unplugged nics (Possible values are: [OK | ok] or [CRITICAL | critical | CRIT | crit] or [WARNING | warning | WARN | warn]. Default is WARNING. Values are case insensitive.) +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-volumes** @@ -4569,31 +4594,32 @@ Check command object for the `check_vmware_esx` plugin. Shows all datastore volu Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_subselect | **Optional.** Volume name to be checked the free space. -vmware_gigabyte | **Optional.** Output in GB instead of MB. -vmware_usedspace | **Optional.** Output used space instead of free. Defaults to "false". -vmware_alertonly | **Optional.** List only alerting volumes. Defaults to "false". -vmware_exclude | **Optional.** Blacklist volumes name. No value defined as default. -vmware_include | **Optional.** Whitelist volumes name. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. -vmware_warn | **Optional.** The warning threshold for volumes. Defaults to "80%". -vmware_crit | **Optional.** The critical threshold for volumes. Defaults to "90%". -vmware_spaceleft | **Optional.** This has to be used in conjunction with thresholds as mentioned above. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_subselect | **Optional.** Volume name to be checked the free space. +vmware_gigabyte | **Optional.** Output in GB instead of MB. +vmware_usedspace | **Optional.** Output used space instead of free. Defaults to "false". +vmware_alertonly | **Optional.** List only alerting volumes. Defaults to "false". +vmware_exclude | **Optional.** Blacklist volumes name. No value defined as default. +vmware_include | **Optional.** Whitelist volumes name. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +vmware_warn | **Optional.** The warning threshold for volumes. Defaults to "80%". +vmware_crit | **Optional.** The critical threshold for volumes. Defaults to "90%". +vmware_spaceleft | **Optional.** This has to be used in conjunction with thresholds as mentioned above. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-io** @@ -4602,21 +4628,22 @@ Check command object for the `check_vmware_esx` plugin. Shows all disk io info. Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-io-aborted** @@ -4625,23 +4652,24 @@ Check command object for the `check_vmware_esx` plugin. Number of aborted SCSI c Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold. No value defined as default. -vmware_crit | **Optional.** The critical threshold. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold. No value defined as default. +vmware_crit | **Optional.** The critical threshold. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-io-resets** @@ -4650,23 +4678,24 @@ Check command object for the `check_vmware_esx` plugin. Number of SCSI bus reset Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold. No value defined as default. -vmware_crit | **Optional.** The critical threshold. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold. No value defined as default. +vmware_crit | **Optional.** The critical threshold. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-io-read** @@ -4675,23 +4704,24 @@ Check command object for the `check_vmware_esx` plugin. Average number of kiloby Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold. No value defined as default. -vmware_crit | **Optional.** The critical threshold. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold. No value defined as default. +vmware_crit | **Optional.** The critical threshold. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-io-read-latency** @@ -4700,23 +4730,24 @@ Check command object for the `check_vmware_esx` plugin. Average amount of time ( Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold. No value defined as default. -vmware_crit | **Optional.** The critical threshold. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold. No value defined as default. +vmware_crit | **Optional.** The critical threshold. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-io-write** @@ -4725,23 +4756,24 @@ Check command object for the `check_vmware_esx` plugin. Average number of kiloby Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold. No value defined as default. -vmware_crit | **Optional.** The critical threshold. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold. No value defined as default. +vmware_crit | **Optional.** The critical threshold. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-io-write-latency** @@ -4750,23 +4782,24 @@ Check command object for the `check_vmware_esx` plugin. Average amount of time ( Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold. No value defined as default. -vmware_crit | **Optional.** The critical threshold. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold. No value defined as default. +vmware_crit | **Optional.** The critical threshold. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-io-usage** @@ -4775,23 +4808,24 @@ Check command object for the `check_vmware_esx` plugin. Aggregated disk I/O rate Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold. No value defined as default. -vmware_crit | **Optional.** The critical threshold. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold. No value defined as default. +vmware_crit | **Optional.** The critical threshold. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-io-kernel-latency** @@ -4800,23 +4834,24 @@ Check command object for the `check_vmware_esx` plugin. Average amount of time ( Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold. No value defined as default. -vmware_crit | **Optional.** The critical threshold. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold. No value defined as default. +vmware_crit | **Optional.** The critical threshold. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-io-device-latency** @@ -4825,23 +4860,24 @@ Check command object for the `check_vmware_esx` plugin. Average amount of time ( Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold. No value defined as default. -vmware_crit | **Optional.** The critical threshold. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold. No value defined as default. +vmware_crit | **Optional.** The critical threshold. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-io-queue-latency** @@ -4850,23 +4886,24 @@ Check command object for the `check_vmware_esx` plugin. Average amount of time ( Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold. No value defined as default. -vmware_crit | **Optional.** The critical threshold. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold. No value defined as default. +vmware_crit | **Optional.** The critical threshold. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-io-total-latency** @@ -4875,23 +4912,24 @@ Check command object for the `check_vmware_esx` plugin. Average amount of time ( Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold. No value defined as default. -vmware_crit | **Optional.** The critical threshold. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold. No value defined as default. +vmware_crit | **Optional.** The critical threshold. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-media** @@ -4900,25 +4938,26 @@ Check command object for the `check_vmware_esx` plugin. List vm's with attached Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_exclude | **Optional.** Blacklist VMs name. No value defined as default. -vmware_include | **Optional.** Whitelist VMs name. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. -vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_exclude | **Optional.** Blacklist VMs name. No value defined as default. +vmware_include | **Optional.** Whitelist VMs name. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-service** @@ -4927,25 +4966,26 @@ Check command object for the `check_vmware_esx` plugin. Shows host service info. Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_exclude | **Optional.** Blacklist services name. No value defined as default. -vmware_include | **Optional.** Whitelist services name. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. -vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_exclude | **Optional.** Blacklist services name. No value defined as default. +vmware_include | **Optional.** Whitelist services name. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-runtime** @@ -4954,24 +4994,25 @@ Check command object for the `check_vmware_esx` plugin. Shows runtime info: VMs, Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_exclude | **Optional.** Blacklist VMs name. No value defined as default. -vmware_include | **Optional.** Whitelist VMs name. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_exclude | **Optional.** Blacklist VMs name. No value defined as default. +vmware_include | **Optional.** Whitelist VMs name. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-runtime-con** @@ -4980,21 +5021,22 @@ Check command object for the `check_vmware_esx` plugin. Shows connection state. Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-runtime-listvms** @@ -5003,25 +5045,26 @@ Check command object for the `check_vmware_esx` plugin. List of VMware machines Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_exclude | **Optional.** Blacklist VMs name. No value defined as default. -vmware_include | **Optional.** Whitelist VMs name. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. -vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_exclude | **Optional.** Blacklist VMs name. No value defined as default. +vmware_include | **Optional.** Whitelist VMs name. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-runtime-status** @@ -5030,21 +5073,22 @@ Check command object for the `check_vmware_esx` plugin. Overall object status (g Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-runtime-health** @@ -5053,24 +5097,25 @@ Check command object for the `check_vmware_esx` plugin. Checks cpu/storage/memor Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_exclude | **Optional.** Blacklist status name. No value defined as default. -vmware_include | **Optional.** Whitelist status name. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_exclude | **Optional.** Blacklist status name. No value defined as default. +vmware_include | **Optional.** Whitelist status name. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-runtime-health-listsensors** @@ -5079,24 +5124,25 @@ Check command object for the `check_vmware_esx` plugin. List all available senso Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_exclude | **Optional.** Blacklist status name. No value defined as default. -vmware_include | **Optional.** Whitelist status name. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_exclude | **Optional.** Blacklist status name. No value defined as default. +vmware_include | **Optional.** Whitelist status name. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-runtime-health-nostoragestatus** @@ -5105,24 +5151,25 @@ Check command object for the `check_vmware_esx` plugin. This is to avoid a doubl Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_exclude | **Optional.** Blacklist status name. No value defined as default. -vmware_include | **Optional.** Whitelist status name. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_exclude | **Optional.** Blacklist status name. No value defined as default. +vmware_include | **Optional.** Whitelist status name. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-runtime-storagehealth** @@ -5131,25 +5178,26 @@ Check command object for the `check_vmware_esx` plugin. Local storage status che Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_exclude | **Optional.** Blacklist storage name. No value defined as default. -vmware_include | **Optional.** Whitelist storage name. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. -vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_exclude | **Optional.** Blacklist storage name. No value defined as default. +vmware_include | **Optional.** Whitelist storage name. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-runtime-temp** @@ -5158,25 +5206,26 @@ Check command object for the `check_vmware_esx` plugin. Lists all temperature se Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_exclude | **Optional.** Blacklist sensor name. No value defined as default. -vmware_include | **Optional.** Whitelist sensor name. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. -vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_exclude | **Optional.** Blacklist sensor name. No value defined as default. +vmware_include | **Optional.** Whitelist sensor name. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-runtime-issues** @@ -5185,25 +5234,26 @@ Check command object for the `check_vmware_esx` plugin. Lists all configuration Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_exclude | **Optional.** Blacklist configuration issues. No value defined as default. -vmware_include | **Optional.** Whitelist configuration issues. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. -vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_exclude | **Optional.** Blacklist configuration issues. No value defined as default. +vmware_include | **Optional.** Whitelist configuration issues. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-storage** @@ -5212,24 +5262,25 @@ Check command object for the `check_vmware_esx` plugin. Shows Host storage info. Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_exclude | **Optional.** Blacklist adapters, luns and paths. No value defined as default. -vmware_include | **Optional.** Whitelist adapters, luns and paths. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_exclude | **Optional.** Blacklist adapters, luns and paths. No value defined as default. +vmware_include | **Optional.** Whitelist adapters, luns and paths. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-storage-adapter** @@ -5238,25 +5289,26 @@ Check command object for the `check_vmware_esx` plugin. List host bus adapters. Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_exclude | **Optional.** Blacklist adapters. No value defined as default. -vmware_include | **Optional.** Whitelist adapters. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. -vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_exclude | **Optional.** Blacklist adapters. No value defined as default. +vmware_include | **Optional.** Whitelist adapters. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-storage-lun** @@ -5265,25 +5317,26 @@ Check command object for the `check_vmware_esx` plugin. List SCSI logical units. Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_exclude | **Optional.** Blacklist luns. No value defined as default. -vmware_include | **Optional.** Whitelist luns. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. -vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_exclude | **Optional.** Blacklist luns. No value defined as default. +vmware_include | **Optional.** Whitelist luns. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-host-storage-path** @@ -5292,27 +5345,28 @@ Check command object for the `check_vmware_esx` plugin. List multipaths and the Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_host | **Required.** ESX or ESXi hostname. -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_alertonly | **Optional.** List only alerting units. Important here to avoid masses of data. Defaults to "false". -vmware_exclude | **Optional.** Blacklist paths. No value defined as default. -vmware_include | **Optional.** Whitelist paths. No value defined as default. -vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. -vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. -vmware_standbyok | **Optional.** For storage systems where a standby multipath is ok and not a warning. Defaults to false. +Name | Description +------------------------------|-------------- +vmware_host | **Required.** ESX or ESXi hostname. +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. In case the check is done through a Datacenter/vCenter host. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_alertonly | **Optional.** List only alerting units. Important here to avoid masses of data. Defaults to "false". +vmware_exclude | **Optional.** Blacklist paths. No value defined as default. +vmware_include | **Optional.** Whitelist paths. No value defined as default. +vmware_isregexp | **Optional.** Treat blacklist and whitelist expressions as regexp. +vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +vmware_standbyok | **Optional.** For storage systems where a standby multipath is ok and not a warning. Defaults to false. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-cpu** @@ -5321,23 +5375,23 @@ Check command object for the `check_vmware_esx` plugin. Shows all CPU usage info Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd - +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-cpu-ready** @@ -5346,24 +5400,25 @@ Check command object for the `check_vmware_esx` plugin. Percentage of time that Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold. No value defined as default. -vmware_crit | **Optional.** The critical threshold. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold. No value defined as default. +vmware_crit | **Optional.** The critical threshold. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-cpu-wait** @@ -5372,24 +5427,25 @@ Check command object for the `check_vmware_esx` plugin. CPU time spent in wait s Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold. No value defined as default. -vmware_crit | **Optional.** The critical threshold. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold. No value defined as default. +vmware_crit | **Optional.** The critical threshold. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-cpu-usage** @@ -5398,24 +5454,25 @@ Check command object for the `check_vmware_esx` plugin. Amount of actively used Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** Warning threshold in percent. Defaults to "80%". -vmware_crit | **Optional.** Critical threshold in percent. Defaults to "90%". +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** Warning threshold in percent. Defaults to "80%". +vmware_crit | **Optional.** Critical threshold in percent. Defaults to "90%". +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-mem** @@ -5424,22 +5481,23 @@ Check command object for the `check_vmware_esx` plugin. Shows all memory info, e Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-mem-usage** @@ -5448,24 +5506,25 @@ Check command object for the `check_vmware_esx` plugin. Average mem usage in per Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** Warning threshold in percent. Defaults to "80%". -vmware_crit | **Optional.** Critical threshold in percent. Defaults to "90%". +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** Warning threshold in percent. Defaults to "80%". +vmware_crit | **Optional.** Critical threshold in percent. Defaults to "90%". +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-mem-consumed** @@ -5475,24 +5534,25 @@ Check command object for the `check_vmware_esx` plugin. Amount of guest physical Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold. No value defined as default. -vmware_crit | **Optional.** The critical threshold. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold. No value defined as default. +vmware_crit | **Optional.** The critical threshold. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-mem-memctl** @@ -5501,25 +5561,25 @@ Check command object for the `check_vmware_esx` plugin. Amount of guest physical Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold. No value defined as default. -vmware_crit | **Optional.** The critical threshold. No value defined as default. - +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold. No value defined as default. +vmware_crit | **Optional.** The critical threshold. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-net** @@ -5528,22 +5588,23 @@ Check command object for the `check_vmware_esx` plugin. Shows net info. Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-net-usage** @@ -5552,24 +5613,25 @@ Check command object for the `check_vmware_esx` plugin. Overall network usage in Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold. No value defined as default. -vmware_crit | **Optional.** The critical threshold. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold. No value defined as default. +vmware_crit | **Optional.** The critical threshold. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-net-receive** @@ -5578,24 +5640,25 @@ Check command object for the `check_vmware_esx` plugin. Receive in KBps(Kilobyte Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold. No value defined as default. -vmware_crit | **Optional.** The critical threshold. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold. No value defined as default. +vmware_crit | **Optional.** The critical threshold. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-net-send** @@ -5604,24 +5667,25 @@ Check command object for the `check_vmware_esx` plugin. Send in KBps(Kilobytes p Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold. No value defined as default. -vmware_crit | **Optional.** The critical threshold. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold. No value defined as default. +vmware_crit | **Optional.** The critical threshold. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-io** @@ -5630,22 +5694,23 @@ Check command object for the `check_vmware_esx` plugin. Shows all disk io info. Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-io-read** @@ -5654,24 +5719,25 @@ Check command object for the `check_vmware_esx` plugin. Average number of kiloby Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session - IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold. No value defined as default. -vmware_crit | **Optional.** The critical threshold. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session - IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold. No value defined as default. +vmware_crit | **Optional.** The critical threshold. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-io-write** @@ -5680,24 +5746,25 @@ Check command object for the `check_vmware_esx` plugin. Average number of kiloby Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold. No value defined as default. -vmware_crit | **Optional.** The critical threshold. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold. No value defined as default. +vmware_crit | **Optional.** The critical threshold. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-io-usage** @@ -5706,24 +5773,25 @@ Check command object for the `check_vmware_esx` plugin. Aggregated disk I/O rate Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold. No value defined as default. -vmware_crit | **Optional.** The critical threshold. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold. No value defined as default. +vmware_crit | **Optional.** The critical threshold. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-runtime** @@ -5732,22 +5800,23 @@ Check command object for the `check_vmware_esx` plugin. Shows virtual machine ru Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-runtime-con** @@ -5756,22 +5825,23 @@ Check command object for the `check_vmware_esx` plugin. Shows the connection sta Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-runtime-powerstate** @@ -5780,22 +5850,23 @@ Check command object for the `check_vmware_esx` plugin. Shows virtual machine po Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-runtime-status** @@ -5804,22 +5875,23 @@ Check command object for the `check_vmware_esx` plugin. Overall object status (g Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-runtime-consoleconnections** @@ -5828,24 +5900,25 @@ Check command object for the `check_vmware_esx` plugin. Console connections to v Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_warn | **Optional.** The warning threshold. No value defined as default. -vmware_crit | **Optional.** The critical threshold. No value defined as default. +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_warn | **Optional.** The warning threshold. No value defined as default. +vmware_crit | **Optional.** The critical threshold. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-runtime-gueststate** @@ -5854,22 +5927,23 @@ Check command object for the `check_vmware_esx` plugin. Guest OS status. Needs V Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-runtime-tools** @@ -5877,24 +5951,25 @@ Check command object for the `check_vmware_esx` plugin. Guest OS status. VMware Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_openvmtools | **Optional** Prevent CRITICAL state for installed and running Open VM Tools. -vmware_novmtools | **Optional** Prevent CRITICAL state for missing VMware tools. +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_openvmtools | **Optional** Prevent CRITICAL state for installed and running Open VM Tools. +vmware_novmtools | **Optional** Prevent CRITICAL state for missing VMware tools. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-soap-vm-runtime-issues** @@ -5903,24 +5978,24 @@ Check command object for the `check_vmware_esx` plugin. All issues for the virtu Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): -Name | Description -------------------------|-------------- -vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. -vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. -vmware_vmname | **Required.** Virtual machine name. -vmware_sslport | **Optional.** SSL port connection. Defaults to "443". -vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". -vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". -vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". -vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. -vmware_sessionfile | **Optional.** Session file name enhancement. -vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". -vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". -vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. -vmware_password | **Optional.** The username's password. No value defined as default. -vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd -vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. - +Name | Description +------------------------------|-------------- +vmware_datacenter | **Optional.** Datacenter/vCenter hostname. Conflicts with **vmware_host**. +vmware_host | **Optional.** ESX or ESXi hostname. Conflicts with **vmware_datacenter**. +vmware_vmname | **Required.** Virtual machine name. +vmware_sslport | **Optional.** SSL port connection. Defaults to "443". +vmware_ignoreunknown | **Optional.** Sometimes 3 (unknown) is returned from a component. But the check itself is ok. With this option the plugin will return OK (0) instead of UNKNOWN (3). Defaults to "false". +vmware_ignorewarning | **Optional.** Sometimes 2 (warning) is returned from a component. But the check itself is ok (from an operator view). With this option the plugin will return OK (0) instead of WARNING (1). Defaults to "false". +vmware_timeout | **Optional.** Seconds before plugin times out. Defaults to "90". +vmware_trace | **Optional.** Set verbosity level of vSphere API request/respond trace. +vmware_sessionfile | **Optional.** Session file name enhancement. +vmware_sessionfiledir | **Optional.** Path to store the **vmware_sessionfile** file. Defaults to "/var/spool/icinga2/tmp". +vmware_nosession | **Optional.** No auth session -- IT SHOULD BE USED FOR TESTING PURPOSES ONLY!. Defaults to "false". +vmware_username | **Optional.** The username to connect to Host or vCenter server. No value defined as default. +vmware_password | **Optional.** The username's password. No value defined as default. +vmware_authfile | **Optional.** Use auth file instead username/password to session connect. No effect if **vmware_username** and **vmware_password** are defined
**Authentication file content:**
username=vmuser
password=p@ssw0rd +vmware_multiline | **Optional.** Multiline output in overview. This mean technically that a multiline output uses a HTML **\** for the GUI. No value defined as default. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. ### Web diff --git a/itl/plugins-contrib.d/vmware.conf b/itl/plugins-contrib.d/vmware.conf index 025cedb11..d32f38959 100644 --- a/itl/plugins-contrib.d/vmware.conf +++ b/itl/plugins-contrib.d/vmware.conf @@ -50,6 +50,10 @@ template CheckCommand "vmware-esx-command" { username= \ password=" } + "--maintenance_mode_state" = { + value = "$vmware_maintenance_mode_state$" + description = "Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive." + } } vars.vmware_timeout = "90" From 946937d6d9771336c4ad0788914e806be3ae15cc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 May 2025 09:32:58 +0000 Subject: [PATCH 302/415] Bump actions/cache from 3 to 4 Bumps [actions/cache](https://github.com/actions/cache) from 3 to 4. - [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/v3...v4) --- updated-dependencies: - dependency-name: actions/cache dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/linux.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 7410c6894..352a7cc71 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -67,7 +67,7 @@ jobs: uses: actions/checkout@v3 - name: Restore/backup ccache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: ccache key: ccache/${{ matrix.distro }} From cf6c2064a08f3ba4fe29a8764b1eebebf3714931 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 16 May 2025 08:49:26 +0000 Subject: [PATCH 303/415] Bump actions/checkout from 1 to 4 Bumps [actions/checkout](https://github.com/actions/checkout) from 1 to 4. - [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/v1...v4) --- updated-dependencies: - dependency-name: actions/checkout dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/authors-file.yml | 2 +- .github/workflows/linux.yml | 2 +- .github/workflows/windows.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/authors-file.yml b/.github/workflows/authors-file.yml index 8824bbdab..724e189cb 100644 --- a/.github/workflows/authors-file.yml +++ b/.github/workflows/authors-file.yml @@ -10,7 +10,7 @@ jobs: steps: - name: Checkout HEAD - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 7410c6894..2f1a63357 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -64,7 +64,7 @@ jobs: steps: - name: Checkout HEAD - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Restore/backup ccache uses: actions/cache@v3 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index b04deaaab..757740503 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout HEAD - uses: actions/checkout@v1 + uses: actions/checkout@v4 - name: Build tools run: | From ef63f43b3fc10f2ba1c369a3b7b57f399e919818 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 16 May 2025 11:15:35 +0200 Subject: [PATCH 304/415] GHA: Fetch full git history for windows --- .github/workflows/windows.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 757740503..db4806915 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -30,6 +30,8 @@ jobs: steps: - name: Checkout HEAD uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Build tools run: | From 72e77177de586da15c7bcfb624309ce5f02f6883 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Fri, 16 May 2025 12:57:56 +0200 Subject: [PATCH 305/415] Markdown: indent 2nd-level
    with 4 spaces, not 1 Neither CLion, nor GitHub or icinga.com differ 0 and 1 spaces before asterisk. --- doc/19-technical-concepts.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/19-technical-concepts.md b/doc/19-technical-concepts.md index 2d25602d8..e5d27ec6f 100644 --- a/doc/19-technical-concepts.md +++ b/doc/19-technical-concepts.md @@ -625,11 +625,11 @@ The algorithm works like this: * If there's two endpoints, but only us seeing ourselves and the application start is less than 60 seconds in the past, do nothing (wait for cluster reconnect to take place, grace period). * Sort the collected endpoints by name. * Iterate over all config types and their respective objects - * Ignore !active objects - * Ignore objects which are !HARunOnce. This means, they can run multiple times in a zone and don't need an authority update. - * If this instance doesn't have a local zone, set authority to true. This is for non-clustered standalone environments where everything belongs to this instance. - * Calculate the object authority based on the connected endpoint names. - * Set the authority (true or false) + * Ignore !active objects + * Ignore objects which are !HARunOnce. This means, they can run multiple times in a zone and don't need an authority update. + * If this instance doesn't have a local zone, set authority to true. This is for non-clustered standalone environments where everything belongs to this instance. + * Calculate the object authority based on the connected endpoint names. + * Set the authority (true or false) The object authority calculation works "offline" without any message exchange. Each instance alculates the SDBM hash of the config object name, puts that in contrast From 6a6c4942796ee317df00a04bf06383affdfe0dc4 Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Mon, 5 May 2025 09:12:41 +0200 Subject: [PATCH 306/415] Mark MakeName and ParseName virtual methods as override --- lib/icinga/comment.ti | 4 ++-- lib/icinga/dependency.ti | 4 ++-- lib/icinga/downtime.ti | 4 ++-- lib/icinga/notification.ti | 4 ++-- lib/icinga/scheduleddowntime.ti | 4 ++-- lib/icinga/service.ti | 4 ++-- 6 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/icinga/comment.ti b/lib/icinga/comment.ti index b8ad6f7f2..8fbb2da17 100644 --- a/lib/icinga/comment.ti +++ b/lib/icinga/comment.ti @@ -24,8 +24,8 @@ enum CommentType class CommentNameComposer : public NameComposer { public: - virtual String MakeName(const String& shortName, const Object::Ptr& context) const; - virtual Dictionary::Ptr ParseName(const String& name) const; + virtual String MakeName(const String& shortName, const Object::Ptr& context) const override; + virtual Dictionary::Ptr ParseName(const String& name) const override; }; }}} diff --git a/lib/icinga/dependency.ti b/lib/icinga/dependency.ti index a033420ea..6d6249d9f 100644 --- a/lib/icinga/dependency.ti +++ b/lib/icinga/dependency.ti @@ -13,8 +13,8 @@ code {{{ class DependencyNameComposer : public NameComposer { public: - virtual String MakeName(const String& shortName, const Object::Ptr& context) const; - virtual Dictionary::Ptr ParseName(const String& name) const; + virtual String MakeName(const String& shortName, const Object::Ptr& context) const override; + virtual Dictionary::Ptr ParseName(const String& name) const override; }; }}} diff --git a/lib/icinga/downtime.ti b/lib/icinga/downtime.ti index 21e97313e..b1530abe1 100644 --- a/lib/icinga/downtime.ti +++ b/lib/icinga/downtime.ti @@ -13,8 +13,8 @@ code {{{ class DowntimeNameComposer : public NameComposer { public: - virtual String MakeName(const String& shortName, const Object::Ptr& context) const; - virtual Dictionary::Ptr ParseName(const String& name) const; + virtual String MakeName(const String& shortName, const Object::Ptr& context) const override; + virtual Dictionary::Ptr ParseName(const String& name) const override; }; }}} diff --git a/lib/icinga/notification.ti b/lib/icinga/notification.ti index a35c4cffb..a8121ff6b 100644 --- a/lib/icinga/notification.ti +++ b/lib/icinga/notification.ti @@ -13,8 +13,8 @@ code {{{ class NotificationNameComposer : public NameComposer { public: - virtual String MakeName(const String& shortName, const Object::Ptr& context) const; - virtual Dictionary::Ptr ParseName(const String& name) const; + virtual String MakeName(const String& shortName, const Object::Ptr& context) const override; + virtual Dictionary::Ptr ParseName(const String& name) const override; }; }}} diff --git a/lib/icinga/scheduleddowntime.ti b/lib/icinga/scheduleddowntime.ti index 1653f27e7..c44228b1c 100644 --- a/lib/icinga/scheduleddowntime.ti +++ b/lib/icinga/scheduleddowntime.ti @@ -12,8 +12,8 @@ code {{{ class ScheduledDowntimeNameComposer : public NameComposer { public: - virtual String MakeName(const String& shortName, const Object::Ptr& context) const; - virtual Dictionary::Ptr ParseName(const String& name) const; + virtual String MakeName(const String& shortName, const Object::Ptr& context) const override; + virtual Dictionary::Ptr ParseName(const String& name) const override; }; }}} diff --git a/lib/icinga/service.ti b/lib/icinga/service.ti index 12c2d8c66..59684d110 100644 --- a/lib/icinga/service.ti +++ b/lib/icinga/service.ti @@ -15,8 +15,8 @@ code {{{ class ServiceNameComposer : public NameComposer { public: - virtual String MakeName(const String& shortName, const Object::Ptr& context) const; - virtual Dictionary::Ptr ParseName(const String& name) const; + virtual String MakeName(const String& shortName, const Object::Ptr& context) const override; + virtual Dictionary::Ptr ParseName(const String& name) const override; }; }}} From f8d3bacc2958dc041cda90629597986b71da8119 Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Tue, 6 May 2025 08:34:32 +0200 Subject: [PATCH 307/415] Fix warnings related to enum integer conversion --- lib/icinga/notification.cpp | 6 ++++-- lib/icingadb/icingadb-objects.cpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/icinga/notification.cpp b/lib/icinga/notification.cpp index d5597065f..63e24b746 100644 --- a/lib/icinga/notification.cpp +++ b/lib/icinga/notification.cpp @@ -472,7 +472,8 @@ void Notification::BeginExecuteNotification(NotificationType type, const CheckRe if (type == NotificationProblem && !reminder && !checkable->GetVolatile()) { auto [host, service] = GetHostService(checkable); - uint_fast8_t state = service ? service->GetState() : host->GetState(); + uint_fast8_t state = service ? static_cast(service->GetState()) + : static_cast(host->GetState()); if (state == (uint_fast8_t)GetLastNotifiedStatePerUser()->Get(userName)) { auto stateStr (service ? NotificationServiceStateToString(service->GetState()) : NotificationHostStateToString(host->GetState())); @@ -501,7 +502,8 @@ void Notification::BeginExecuteNotification(NotificationType type, const CheckRe if (type == NotificationProblem) { auto [host, service] = GetHostService(checkable); - uint_fast8_t state = service ? service->GetState() : host->GetState(); + uint_fast8_t state = service ? static_cast(service->GetState()) + : static_cast(host->GetState()); if (state != (uint_fast8_t)GetLastNotifiedStatePerUser()->Get(userName)) { GetLastNotifiedStatePerUser()->Set(userName, state); diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index 914d46bc0..56b74a3c1 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -2061,7 +2061,7 @@ void IcingaDB::SendSentNotification( "host_id", GetObjectIdentifier(host), "type", Convert::ToString(type), "state", Convert::ToString(cr ? service ? Convert::ToLong(cr->GetState()) : Convert::ToLong(Host::CalculateState(cr->GetState())) : 99), - "previous_hard_state", Convert::ToString(cr ? Convert::ToLong(service ? cr->GetPreviousHardState() : Host::CalculateState(cr->GetPreviousHardState())) : 99), + "previous_hard_state", Convert::ToString(cr ? service ? Convert::ToLong(cr->GetPreviousHardState()) : Convert::ToLong(Host::CalculateState(cr->GetPreviousHardState())) : 99), "author", Utility::ValidateUTF8(author), "text", Utility::ValidateUTF8(finalText), "users_notified", Convert::ToString(usersAmount), From 0c2215a9f84bacd112c621d9c8b54f00ff79a207 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 19 May 2025 14:07:33 +0200 Subject: [PATCH 308/415] Checkable: Use correct timeout for rescheduling remote checks Previously, the `command#timeout` which by default is `1m`, was used to reschedule the just sent remote check. However, this results into a bunch of extra checks being sent to the remote host, even though the first one is still running. That's because if one want to override the default timeout of the command for a specific host/service, one has to set the `checkable#check_timeout` attribute to the desired value. So, this commit makes sure that the `checkable#check_timeout` attribute (if set) is used to reschedule the remote check. --- lib/icinga/checkable-check.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/icinga/checkable-check.cpp b/lib/icinga/checkable-check.cpp index 2e96df5af..fdd3914c7 100644 --- a/lib/icinga/checkable-check.cpp +++ b/lib/icinga/checkable-check.cpp @@ -624,12 +624,16 @@ void Checkable::ExecuteCheck() if (service) params->Set("service", service->GetShortName()); + double checkTimeout = GetCheckCommand()->GetTimeout(); + /* * If the host/service object specifies the 'check_timeout' attribute, * forward this to the remote endpoint to limit the command execution time. */ - if (!GetCheckTimeout().IsEmpty()) - params->Set("check_timeout", GetCheckTimeout()); + if (auto ckCheckTimeout(GetCheckTimeout()); !ckCheckTimeout.IsEmpty()) { + checkTimeout = Convert::ToDouble(ckCheckTimeout); + params->Set("check_timeout", ckCheckTimeout); + } params->Set("macros", macros); @@ -642,7 +646,7 @@ void Checkable::ExecuteCheck() * a check result from the remote instance. The check will be re-scheduled * using the proper check interval once we've received a check result. */ - SetNextCheck(Utility::GetTime() + GetCheckCommand()->GetTimeout() + 30); + SetNextCheck(Utility::GetTime() + checkTimeout + 30); /* * Let the user know that there was a problem with the check if From 55fc0e51ffc935f8cad6f1c8105b3884cf72d14c Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 20 May 2025 10:33:20 +0200 Subject: [PATCH 309/415] Checkable#process_check_result(): don't pass NULL CR to Checkable#ProcessCheckResult() It's ignored anyway. --- lib/icinga/checkable-script.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/icinga/checkable-script.cpp b/lib/icinga/checkable-script.cpp index fe08f32d3..764a9f851 100644 --- a/lib/icinga/checkable-script.cpp +++ b/lib/icinga/checkable-script.cpp @@ -14,7 +14,10 @@ static void CheckableProcessCheckResult(const CheckResult::Ptr& cr) ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); Checkable::Ptr self = vframe->Self; REQUIRE_NOT_NULL(self); - self->ProcessCheckResult(cr); + + if (cr) { + self->ProcessCheckResult(cr); + } } Object::Ptr Checkable::GetPrototype() From 69d2a0442a77e48442bd843e0fb9f82a9e9a88f3 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 20 May 2025 10:41:26 +0200 Subject: [PATCH 310/415] Remove unused ProcessingResult::NoCheckResult No one passes a NULL CR to Checkable#ProcessCheckResult() anymore. --- lib/icinga/apiactions.cpp | 2 -- lib/icinga/checkable-check.cpp | 5 ++--- lib/icinga/checkable.hpp | 1 - 3 files changed, 2 insertions(+), 6 deletions(-) diff --git a/lib/icinga/apiactions.cpp b/lib/icinga/apiactions.cpp index 3892de776..0f7e5cff1 100644 --- a/lib/icinga/apiactions.cpp +++ b/lib/icinga/apiactions.cpp @@ -130,8 +130,6 @@ Dictionary::Ptr ApiActions::ProcessCheckResult(const ConfigObject::Ptr& object, switch (result) { case Result::Ok: return ApiActions::CreateResult(200, "Successfully processed check result for object '" + checkable->GetName() + "'."); - case Result::NoCheckResult: - return ApiActions::CreateResult(400, "Could not process check result for object '" + checkable->GetName() + "' because no check result was passed."); case Result::CheckableInactive: return ApiActions::CreateResult(503, "Could not process check result for object '" + checkable->GetName() + "' because the object is inactive."); case Result::NewerCheckResultPresent: diff --git a/lib/icinga/checkable-check.cpp b/lib/icinga/checkable-check.cpp index 2e96df5af..868921dc9 100644 --- a/lib/icinga/checkable-check.cpp +++ b/lib/icinga/checkable-check.cpp @@ -100,14 +100,13 @@ Checkable::ProcessingResult Checkable::ProcessCheckResult(const CheckResult::Ptr { using Result = Checkable::ProcessingResult; + VERIFY(cr); + { ObjectLock olock(this); m_CheckRunning = false; } - if (!cr) - return Result::NoCheckResult; - double now = Utility::GetTime(); if (cr->GetScheduleStart() == 0) diff --git a/lib/icinga/checkable.hpp b/lib/icinga/checkable.hpp index 98c015ed6..da6be5630 100644 --- a/lib/icinga/checkable.hpp +++ b/lib/icinga/checkable.hpp @@ -119,7 +119,6 @@ public: enum class ProcessingResult { Ok, - NoCheckResult, CheckableInactive, NewerCheckResultPresent, }; From 828f18f6506d45e0577f466775ed9161bdd09616 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 20 May 2025 12:14:56 +0200 Subject: [PATCH 311/415] Bump Windows OpenSSL version to `3.0.16` --- doc/win-dev.ps1 | 2 +- tools/win32/configure.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/win-dev.ps1 b/doc/win-dev.ps1 index e3b4e5955..5729343e3 100644 --- a/doc/win-dev.ps1 +++ b/doc/win-dev.ps1 @@ -14,7 +14,7 @@ function ThrowOnNativeFailure { $VsVersion = 2019 $MsvcVersion = '14.2' $BoostVersion = @(1, 87, 0) -$OpensslVersion = '3_0_15' +$OpensslVersion = '3_0_16' switch ($Env:BITS) { 32 { } diff --git a/tools/win32/configure.ps1 b/tools/win32/configure.ps1 index 52d8628a1..008a06de0 100644 --- a/tools/win32/configure.ps1 +++ b/tools/win32/configure.ps1 @@ -33,7 +33,7 @@ if (-not (Test-Path env:CMAKE_ARGS)) { $env:CMAKE_ARGS = '[]' } if (-not (Test-Path env:OPENSSL_ROOT_DIR)) { - $env:OPENSSL_ROOT_DIR = "c:\local\OpenSSL_3_0_15-Win${env:BITS}" + $env:OPENSSL_ROOT_DIR = "c:\local\OpenSSL_3_0_16-Win${env:BITS}" } if (-not (Test-Path env:BOOST_ROOT)) { $env:BOOST_ROOT = "c:\local\boost_1_87_0-Win${env:BITS}" From 5e23a4f0987cd0958f9aa6777101a7d591b90f6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Tue, 20 May 2025 12:44:04 +0200 Subject: [PATCH 312/415] 19-technical-concepts.md: correct cold startup duration It's 30s since 149f640fd8e9191efcf69a60e8ce83f38101d359. --- doc/19-technical-concepts.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/19-technical-concepts.md b/doc/19-technical-concepts.md index e5d27ec6f..583536864 100644 --- a/doc/19-technical-concepts.md +++ b/doc/19-technical-concepts.md @@ -622,7 +622,8 @@ The algorithm works like this: * Determine whether this instance is assigned to a local zone and endpoint. * Collects all endpoints in this zone if they are connected. -* If there's two endpoints, but only us seeing ourselves and the application start is less than 60 seconds in the past, do nothing (wait for cluster reconnect to take place, grace period). +* If there's two endpoints, but only us seeing ourselves and the application start is less than + 30 seconds in the past, do nothing (wait for cluster reconnect to take place, grace period). * Sort the collected endpoints by name. * Iterate over all config types and their respective objects * Ignore !active objects From 00864d1096ad52b92fb1e79294b1bd09eb7730fa Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Wed, 14 May 2025 10:16:34 +0200 Subject: [PATCH 313/415] VerifyCertificate: fix use after free `X509_STORE_CTX_get_error(csc)` was called after `X509_STORE_CTX_free(csc)`. This is fixed by automatically freeing variables at the end of the function using `std::unique_ptr`. --- lib/base/tlsutility.cpp | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/base/tlsutility.cpp b/lib/base/tlsutility.cpp index fb60e0221..9942e0921 100644 --- a/lib/base/tlsutility.cpp +++ b/lib/base/tlsutility.cpp @@ -985,27 +985,24 @@ String BinaryToHex(const unsigned char* data, size_t length) { bool VerifyCertificate(const std::shared_ptr &caCertificate, const std::shared_ptr &certificate, const String& crlFile) { - X509_STORE *store = X509_STORE_new(); + std::unique_ptr store{X509_STORE_new(), &X509_STORE_free}; if (!store) return false; - X509_STORE_add_cert(store, caCertificate.get()); + X509_STORE_add_cert(store.get(), caCertificate.get()); if (!crlFile.IsEmpty()) { - AddCRLToSSLContext(store, crlFile); + AddCRLToSSLContext(store.get(), crlFile); } - X509_STORE_CTX *csc = X509_STORE_CTX_new(); - X509_STORE_CTX_init(csc, store, certificate.get(), nullptr); + std::unique_ptr csc{X509_STORE_CTX_new(), &X509_STORE_CTX_free}; + X509_STORE_CTX_init(csc.get(), store.get(), certificate.get(), nullptr); - int rc = X509_verify_cert(csc); - - X509_STORE_CTX_free(csc); - X509_STORE_free(store); + int rc = X509_verify_cert(csc.get()); if (rc == 0) { - int err = X509_STORE_CTX_get_error(csc); + int err = X509_STORE_CTX_get_error(csc.get()); BOOST_THROW_EXCEPTION(openssl_error() << boost::errinfo_api_function("X509_verify_cert") From 4023128be42b18a011dda71ddee9ca79955b89cb Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Wed, 14 May 2025 11:17:22 +0200 Subject: [PATCH 314/415] VerifyCertificate: Work around issue in OpenSSL < 1.1.0 causing invalid certifcates being treated as valid Old versions of OpenSSL stored a valid flag in the certificate (see inline code comment for details) that if already set, causes parts of the verification to be skipped and return that the certificate is valid, even if it's not actually signed by the CA in the trust store. This issue was assigned CVE-2025-48057. --- lib/base/tlsutility.cpp | 27 +++++++++++++++++++++++++-- lib/base/tlsutility.hpp | 1 + test/CMakeLists.txt | 1 + test/base-tlsutility.cpp | 20 ++++++++++++++++++++ 4 files changed, 47 insertions(+), 2 deletions(-) diff --git a/lib/base/tlsutility.cpp b/lib/base/tlsutility.cpp index 9942e0921..fce718126 100644 --- a/lib/base/tlsutility.cpp +++ b/lib/base/tlsutility.cpp @@ -985,19 +985,42 @@ String BinaryToHex(const unsigned char* data, size_t length) { bool VerifyCertificate(const std::shared_ptr &caCertificate, const std::shared_ptr &certificate, const String& crlFile) { + return VerifyCertificate(caCertificate.get(), certificate.get(), crlFile); +} + +bool VerifyCertificate(X509* caCertificate, X509* certificate, const String& crlFile) +{ +#if OPENSSL_VERSION_NUMBER < 0x10100000L + /* + * OpenSSL older than version 1.1.0 stored a valid flag in the struct behind X509* which leads to certain validation + * steps to be skipped on subsequent verification operations. If a certificate is verified multiple times with a + * different configuration, for example with different trust anchors, this can result in the certificate + * incorrectly being treated as valid. + * + * This issue is worked around by serializing and deserializing the certificate which creates a new struct instance + * with the valid flag cleared, hence performing the full validation. + * + * The flag in question was removed in OpenSSL 1.1.0, so this extra step isn't necessary for more recent versions: + * https://github.com/openssl/openssl/commit/0e76014e584ba78ef1d6ecb4572391ef61c4fb51 + */ + std::shared_ptr copy = StringToCertificate(CertificateToString(certificate)); + VERIFY(copy.get() != certificate); + certificate = copy.get(); +#endif + std::unique_ptr store{X509_STORE_new(), &X509_STORE_free}; if (!store) return false; - X509_STORE_add_cert(store.get(), caCertificate.get()); + X509_STORE_add_cert(store.get(), caCertificate); if (!crlFile.IsEmpty()) { AddCRLToSSLContext(store.get(), crlFile); } std::unique_ptr csc{X509_STORE_CTX_new(), &X509_STORE_CTX_free}; - X509_STORE_CTX_init(csc.get(), store.get(), certificate.get(), nullptr); + X509_STORE_CTX_init(csc.get(), store.get(), certificate, nullptr); int rc = X509_verify_cert(csc.get()); diff --git a/lib/base/tlsutility.hpp b/lib/base/tlsutility.hpp index b06412020..a498eca12 100644 --- a/lib/base/tlsutility.hpp +++ b/lib/base/tlsutility.hpp @@ -79,6 +79,7 @@ String RandomString(int length); String BinaryToHex(const unsigned char* data, size_t length); bool VerifyCertificate(const std::shared_ptr& caCertificate, const std::shared_ptr& certificate, const String& crlFile); +bool VerifyCertificate(X509* caCertificate, X509* certificate, const String& crlFile); bool IsCa(const std::shared_ptr& cacert); int GetCertificateVersion(const std::shared_ptr& cert); String GetSignatureAlgorithm(const std::shared_ptr& cert); diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0e2a2976e..c87679b08 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -178,6 +178,7 @@ add_boost_test(base base_tlsutility/iscertuptodate_ok base_tlsutility/iscertuptodate_expiring base_tlsutility/iscertuptodate_old + base_tlsutility/VerifyCertificate_revalidate base_utility/parse_version base_utility/compare_version base_utility/comparepasswords_works diff --git a/test/base-tlsutility.cpp b/test/base-tlsutility.cpp index 2e611e49a..4aa4f646f 100644 --- a/test/base-tlsutility.cpp +++ b/test/base-tlsutility.cpp @@ -132,4 +132,24 @@ BOOST_AUTO_TEST_CASE(iscertuptodate_old) }))); } +BOOST_AUTO_TEST_CASE(VerifyCertificate_revalidate) +{ + X509_NAME *caSubject = X509_NAME_new(); + X509_NAME_add_entry_by_txt(caSubject, "CN", MBSTRING_ASC, (const unsigned char*)"Icinga CA", -1, -1, 0); + + auto signingCaKey = GenKeypair(); + auto signingCaCert = CreateCert(signingCaKey, caSubject, caSubject, signingCaKey, true); + + X509_NAME *leafSubject = X509_NAME_new(); + X509_NAME_add_entry_by_txt(leafSubject, "CN", MBSTRING_ASC, (const unsigned char*)"Leaf Certificate", -1, -1, 0); + auto leafKey = GenKeypair(); + auto leafCert = CreateCert(leafKey, leafSubject, caSubject, signingCaKey, false); + BOOST_CHECK(VerifyCertificate(signingCaCert, leafCert, "")); + + // Create a second CA with a different key, the leaf certificate is supposed to fail validation against that CA. + auto otherCaKey = GenKeypair(); + auto otherCaCert = CreateCert(otherCaKey, caSubject, caSubject, otherCaKey, true); + BOOST_CHECK_THROW(VerifyCertificate(otherCaCert, leafCert, ""), openssl_error); +} + BOOST_AUTO_TEST_SUITE_END() From 22e75f08faf0c03d444daff432c445484672e8f9 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 30 Jun 2022 18:52:16 +0200 Subject: [PATCH 315/415] Fix compiler warnings by not unnecessarily (copy-)constructing loop variables --- lib/base/process.cpp | 3 +-- lib/cli/featureutility.cpp | 4 ++-- lib/icinga/host.cpp | 3 +-- lib/icinga/notification.cpp | 3 +-- lib/livestatus/commandstable.cpp | 6 +++--- lib/perfdata/opentsdbwriter.cpp | 4 ++-- lib/remote/apilistener-configsync.cpp | 4 ++-- lib/remote/consolehandler.cpp | 4 +--- lib/remote/eventqueue.cpp | 6 ++---- tools/mkclass/classcompiler.cpp | 4 ++-- 10 files changed, 17 insertions(+), 24 deletions(-) diff --git a/lib/base/process.cpp b/lib/base/process.cpp index 23b735352..aa6a0259e 100644 --- a/lib/base/process.cpp +++ b/lib/base/process.cpp @@ -643,8 +643,7 @@ void Process::IOThreadProc(int tid) #endif /* _WIN32 */ int i = 1; - typedef std::pair kv_pair; - for (const kv_pair& kv : l_Processes[tid]) { + for (auto& kv : l_Processes[tid]) { const Process::Ptr& process = kv.second; #ifdef _WIN32 handles[i] = kv.first; diff --git a/lib/cli/featureutility.cpp b/lib/cli/featureutility.cpp index 3523868c5..94929b5a2 100644 --- a/lib/cli/featureutility.cpp +++ b/lib/cli/featureutility.cpp @@ -58,7 +58,7 @@ int FeatureUtility::EnableFeatures(const std::vector& features) std::vector errors; - for (const String& feature : features) { + for (auto& feature : features) { String source = features_available_dir + "/" + feature + ".conf"; if (!Utility::PathExists(source) ) { @@ -126,7 +126,7 @@ int FeatureUtility::DisableFeatures(const std::vector& features) std::vector errors; - for (const String& feature : features) { + for (auto& feature : features) { String target = features_enabled_dir + "/" + feature + ".conf"; if (!Utility::PathExists(target) ) { diff --git a/lib/icinga/host.cpp b/lib/icinga/host.cpp index 5bc0cead7..10cd4b445 100644 --- a/lib/icinga/host.cpp +++ b/lib/icinga/host.cpp @@ -88,8 +88,7 @@ std::vector Host::GetServices() const std::vector services; services.reserve(m_Services.size()); - typedef std::pair ServicePair; - for (const ServicePair& kv : m_Services) { + for (auto& kv : m_Services) { services.push_back(kv.second); } diff --git a/lib/icinga/notification.cpp b/lib/icinga/notification.cpp index b8cb62ff0..bec03983d 100644 --- a/lib/icinga/notification.cpp +++ b/lib/icinga/notification.cpp @@ -643,8 +643,7 @@ String Notification::NotificationFilterToString(int filter, const std::map sFilters; - typedef std::pair kv_pair; - for (const kv_pair& kv : filterMap) { + for (auto& kv : filterMap) { if (filter & kv.second) sFilters.push_back(kv.first); } diff --git a/lib/livestatus/commandstable.cpp b/lib/livestatus/commandstable.cpp index 3a777d231..82701da81 100644 --- a/lib/livestatus/commandstable.cpp +++ b/lib/livestatus/commandstable.cpp @@ -42,17 +42,17 @@ String CommandsTable::GetPrefix() const void CommandsTable::FetchRows(const AddRowFunction& addRowFn) { - for (const ConfigObject::Ptr& object : ConfigType::GetObjectsByType()) { + for (auto& object : ConfigType::GetObjectsByType()) { if (!addRowFn(object, LivestatusGroupByNone, Empty)) return; } - for (const ConfigObject::Ptr& object : ConfigType::GetObjectsByType()) { + for (auto& object : ConfigType::GetObjectsByType()) { if (!addRowFn(object, LivestatusGroupByNone, Empty)) return; } - for (const ConfigObject::Ptr& object : ConfigType::GetObjectsByType()) { + for (auto& object : ConfigType::GetObjectsByType()) { if (!addRowFn(object, LivestatusGroupByNone, Empty)) return; } diff --git a/lib/perfdata/opentsdbwriter.cpp b/lib/perfdata/opentsdbwriter.cpp index 461e1bfdd..1ebc139f4 100644 --- a/lib/perfdata/opentsdbwriter.cpp +++ b/lib/perfdata/opentsdbwriter.cpp @@ -376,8 +376,8 @@ void OpenTsdbWriter::SendMetric(const Checkable::Ptr& checkable, const String& m { String tags_string = ""; - for (const Dictionary::Pair& tag : tags) { - tags_string += " " + tag.first + "=" + Convert::ToString(tag.second); + for (auto& tag : tags) { + tags_string += " " + tag.first + "=" + tag.second; } std::ostringstream msgbuf; diff --git a/lib/remote/apilistener-configsync.cpp b/lib/remote/apilistener-configsync.cpp index 6d97258ec..063fbd6c2 100644 --- a/lib/remote/apilistener-configsync.cpp +++ b/lib/remote/apilistener-configsync.cpp @@ -133,7 +133,7 @@ Value ApiListener::ConfigUpdateObjectAPIHandler(const MessageOrigin::Ptr& origin << "Could not create object '" << objName << "':"; ObjectLock olock(errors); - for (const String& error : errors) { + for (auto& error : errors) { Log(LogCritical, "ApiListener", error); } @@ -300,7 +300,7 @@ Value ApiListener::ConfigDeleteObjectAPIHandler(const MessageOrigin::Ptr& origin Log(LogCritical, "ApiListener", "Could not delete object:"); ObjectLock olock(errors); - for (const String& error : errors) { + for (auto& error : errors) { Log(LogCritical, "ApiListener", error); } } diff --git a/lib/remote/consolehandler.cpp b/lib/remote/consolehandler.cpp index f5a470a9a..8ed36311c 100644 --- a/lib/remote/consolehandler.cpp +++ b/lib/remote/consolehandler.cpp @@ -32,9 +32,7 @@ static void ScriptFrameCleanupHandler() std::vector cleanup_keys; - typedef std::pair KVPair; - - for (const KVPair& kv : l_ApiScriptFrames) { + for (auto& kv : l_ApiScriptFrames) { if (kv.second.Seen < Utility::GetTime() - 1800) cleanup_keys.push_back(kv.first); } diff --git a/lib/remote/eventqueue.cpp b/lib/remote/eventqueue.cpp index 4705d4050..819f95a6a 100644 --- a/lib/remote/eventqueue.cpp +++ b/lib/remote/eventqueue.cpp @@ -44,8 +44,7 @@ void EventQueue::ProcessEvent(const Dictionary::Ptr& event) std::unique_lock lock(m_Mutex); - typedef std::pair > kv_pair; - for (kv_pair& kv : m_Events) { + for (auto& kv : m_Events) { kv.second.push_back(event); } @@ -108,8 +107,7 @@ std::vector EventQueue::GetQueuesForType(const String& type) std::vector availQueues; - typedef std::pair kv_pair; - for (const kv_pair& kv : queues) { + for (auto& kv : queues) { if (kv.second->CanProcessEvent(type)) availQueues.push_back(kv.second); } diff --git a/tools/mkclass/classcompiler.cpp b/tools/mkclass/classcompiler.cpp index 693011369..755922816 100644 --- a/tools/mkclass/classcompiler.cpp +++ b/tools/mkclass/classcompiler.cpp @@ -902,7 +902,7 @@ void ClassCompiler::HandleClass(const Klass& klass, const ClassDebugInfo&) if (field.Type.ArrayRank > 0) { m_Impl << "\t" << "if (oldValue) {" << std::endl << "\t\t" << "ObjectLock olock(oldValue);" << std::endl - << "\t\t" << "for (const String& ref : oldValue) {" << std::endl + << "\t\t" << "for (auto& ref : oldValue) {" << std::endl << "\t\t\tDependencyGraph::RemoveDependency("; /* Ew */ @@ -916,7 +916,7 @@ void ClassCompiler::HandleClass(const Klass& klass, const ClassDebugInfo&) << "\t" << "}" << std::endl << "\t" << "if (newValue) {" << std::endl << "\t\t" << "ObjectLock olock(newValue);" << std::endl - << "\t\t" << "for (const String& ref : newValue) {" << std::endl + << "\t\t" << "for (auto& ref : newValue) {" << std::endl << "\t\t\tDependencyGraph::AddDependency("; /* Ew */ From 78ded7423f49a6c77036bdeb67683ab5571c4f72 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Wed, 21 May 2025 12:04:24 +0200 Subject: [PATCH 316/415] Always compile with -Wrange-loop-construct This enables warnings when range-based for-loops unnecessarily make copies of elements instead of binding them by reference. --- CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fce41f411..86abc77d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -237,7 +237,7 @@ endif() set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_RPATH};${CMAKE_INSTALL_FULL_LIBDIR}/icinga2") if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Winconsistent-missing-override") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Winconsistent-missing-override -Wrange-loop-construct") set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Qunused-arguments -fcolor-diagnostics -fno-limit-debug-info") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Qunused-arguments -fcolor-diagnostics -fno-limit-debug-info") @@ -258,6 +258,10 @@ endif() if(CMAKE_C_COMPILER_ID STREQUAL "GNU") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-override") + if("${CMAKE_CXX_COMPILER_VERSION}" VERSION_GREATER_EQUAL "11.0.0") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wrange-loop-construct") + endif() + if(CMAKE_SYSTEM_NAME MATCHES AIX) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -lpthread") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -lpthread") From 18f810a1eae1bd872c1ac6e405743136cee9e47e Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 22 May 2025 14:59:19 +0200 Subject: [PATCH 317/415] For the same zone, call ApiListener::UpdateObjectAuthority() in icinga::Hello after setting remote capabilities. They'll become important for teamwork in a zone. --- lib/remote/apilistener.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index db024c987..98b5c4968 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -1794,6 +1794,10 @@ Value ApiListener::HelloAPIHandler(const MessageOrigin::Ptr& origin, const Dicti endpoint->SetIcingaVersion(nodeVersion); endpoint->SetCapabilities((double)params->Get("capabilities")); + if (endpoint->GetZone() == Zone::GetLocalZone()) { + UpdateObjectAuthority(); + } + if (nodeVersion == 0u) { nodeVersion = 21200; } From 6cd83ba2b8c5efd562aede1cc117f746c1abee66 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 16 May 2025 15:02:56 +0200 Subject: [PATCH 318/415] ApiListener::UpdateObjectAuthority(): distribute auth. by object's host Pin child objects of hosts (HOST!...) to the same endpoint as the host. This reduces cross-object action latency withing the same host. If all endpoints know this algorithm, we can use it. --- lib/remote/apilistener-authority.cpp | 25 ++++++++++++++++++++++--- lib/remote/apilistener.cpp | 4 +++- lib/remote/apilistener.hpp | 1 + 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/remote/apilistener-authority.cpp b/lib/remote/apilistener-authority.cpp index f33a1905b..e169ad4b1 100644 --- a/lib/remote/apilistener-authority.cpp +++ b/lib/remote/apilistener-authority.cpp @@ -25,6 +25,7 @@ void ApiListener::UpdateObjectAuthority() std::vector endpoints; Endpoint::Ptr my_endpoint; + int hostChildrenInheritObjectAuthority = 0; if (my_zone) { my_endpoint = Endpoint::GetLocalEndpoint(); @@ -38,6 +39,10 @@ void ApiListener::UpdateObjectAuthority() continue; endpoints.push_back(endpoint); + + if (endpoint == my_endpoint || endpoint->GetCapabilities() & static_cast(ApiCapabilities::HostChildrenInheritObjectAuthority)) { + ++hostChildrenInheritObjectAuthority; + } } double startTime = Application::GetStartTime(); @@ -65,10 +70,24 @@ void ApiListener::UpdateObjectAuthority() bool authority; - if (!my_zone) + if (my_zone) { + auto name (object->GetName()); + + // If all endpoints know this algorithm, we can use it. + if (hostChildrenInheritObjectAuthority == endpoints.size()) { + auto exclamation (name.FindFirstOf('!')); + + // Pin child objects of hosts (HOST!...) to the same endpoint as the host. + // This reduces cross-object action latency withing the same host. + if (exclamation != String::NPos) { + name.erase(name.Begin() + exclamation, name.End()); + } + } + + authority = endpoints[Utility::SDBM(name) % endpoints.size()] == my_endpoint; + } else { authority = true; - else - authority = endpoints[Utility::SDBM(object->GetName()) % endpoints.size()] == my_endpoint; + } #ifdef I2_DEBUG // //Enable on demand, causes heavy logging on each run. diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 98b5c4968..4c0d006d8 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -640,7 +640,9 @@ static const auto l_AppVersionInt (([]() -> unsigned long { })()); static const auto l_MyCapabilities ( - (uint_fast64_t)ApiCapabilities::ExecuteArbitraryCommand | (uint_fast64_t)ApiCapabilities::IfwApiCheckCommand + (uint_fast64_t)ApiCapabilities::ExecuteArbitraryCommand + | (uint_fast64_t)ApiCapabilities::IfwApiCheckCommand + | (uint_fast64_t)ApiCapabilities::HostChildrenInheritObjectAuthority ); /** diff --git a/lib/remote/apilistener.hpp b/lib/remote/apilistener.hpp index eae1fa03e..bdb0fea52 100644 --- a/lib/remote/apilistener.hpp +++ b/lib/remote/apilistener.hpp @@ -69,6 +69,7 @@ enum class ApiCapabilities : uint_fast64_t { ExecuteArbitraryCommand = 1u << 0u, IfwApiCheckCommand = 1u << 1u, + HostChildrenInheritObjectAuthority = 1u << 2u, }; /** From 77b86bba52d76c051fcacd952d672cd6744d91f7 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 19 May 2025 15:25:21 +0200 Subject: [PATCH 319/415] Move l_MyCapabilities -> ApiCapabilities::MyCapabilities --- lib/remote/apilistener.cpp | 10 ++-------- lib/remote/apilistener.hpp | 2 ++ 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 4c0d006d8..1ba8ccdae 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -639,12 +639,6 @@ static const auto l_AppVersionInt (([]() -> unsigned long { + boost::lexical_cast(match[3].str()); })()); -static const auto l_MyCapabilities ( - (uint_fast64_t)ApiCapabilities::ExecuteArbitraryCommand - | (uint_fast64_t)ApiCapabilities::IfwApiCheckCommand - | (uint_fast64_t)ApiCapabilities::HostChildrenInheritObjectAuthority -); - /** * Processes a new client connection. * @@ -775,7 +769,7 @@ void ApiListener::NewClientHandlerInternal( { "method", "icinga::Hello" }, { "params", new Dictionary({ { "version", (double)l_AppVersionInt }, - { "capabilities", (double)l_MyCapabilities } + { "capabilities", (double)ApiCapabilities::MyCapabilities } }) } }), yc); @@ -814,7 +808,7 @@ void ApiListener::NewClientHandlerInternal( { "method", "icinga::Hello" }, { "params", new Dictionary({ { "version", (double)l_AppVersionInt }, - { "capabilities", (double)l_MyCapabilities } + { "capabilities", (double)ApiCapabilities::MyCapabilities } }) } }), yc); diff --git a/lib/remote/apilistener.hpp b/lib/remote/apilistener.hpp index bdb0fea52..9a38a57b5 100644 --- a/lib/remote/apilistener.hpp +++ b/lib/remote/apilistener.hpp @@ -70,6 +70,8 @@ enum class ApiCapabilities : uint_fast64_t ExecuteArbitraryCommand = 1u << 0u, IfwApiCheckCommand = 1u << 1u, HostChildrenInheritObjectAuthority = 1u << 2u, + + MyCapabilities = ExecuteArbitraryCommand | IfwApiCheckCommand | HostChildrenInheritObjectAuthority }; /** From 18fb93fc114163e15b841db1cae32622c2daab15 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 22 May 2025 17:11:23 +0200 Subject: [PATCH 320/415] Introduce WaitGroup and StoppableWaitGroup --- lib/base/CMakeLists.txt | 1 + lib/base/wait-group.cpp | 38 +++++++++++++++++++++++++++++ lib/base/wait-group.hpp | 54 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 lib/base/wait-group.cpp create mode 100644 lib/base/wait-group.hpp diff --git a/lib/base/CMakeLists.txt b/lib/base/CMakeLists.txt index 59e836443..d44254a35 100644 --- a/lib/base/CMakeLists.txt +++ b/lib/base/CMakeLists.txt @@ -87,6 +87,7 @@ set(base_SOURCES unixsocket.cpp unixsocket.hpp utility.cpp utility.hpp value.cpp value.hpp value-operators.cpp + wait-group.cpp wait-group.hpp win32.hpp workqueue.cpp workqueue.hpp ) diff --git a/lib/base/wait-group.cpp b/lib/base/wait-group.cpp new file mode 100644 index 000000000..1e1ad00ee --- /dev/null +++ b/lib/base/wait-group.cpp @@ -0,0 +1,38 @@ +/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */ + +#include "base/wait-group.hpp" + +using namespace icinga; + +bool StoppableWaitGroup::try_lock_shared() +{ + std::unique_lock lock (m_Mutex); + + if (m_Stopped) { + return false; + } + + ++m_SharedLocks; + return true; +} + +void StoppableWaitGroup::unlock_shared() +{ + std::unique_lock lock (m_Mutex); + + if (!--m_SharedLocks && m_Stopped) { + lock.unlock(); + m_CV.notify_all(); + } +} + +/** + * Disallow new shared locks, wait for all existing ones. + */ +void StoppableWaitGroup::Join() +{ + std::unique_lock lock (m_Mutex); + + m_Stopped = true; + m_CV.wait(lock, [this] { return !m_SharedLocks; }); +} diff --git a/lib/base/wait-group.hpp b/lib/base/wait-group.hpp new file mode 100644 index 000000000..5b4527011 --- /dev/null +++ b/lib/base/wait-group.hpp @@ -0,0 +1,54 @@ +/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */ + +#pragma once + +#include "base/object.hpp" +#include +#include +#include + +namespace icinga +{ + +/** + * A synchronization interface that allows concurrent shared locking. + * + * @ingroup base + */ +class WaitGroup : public Object +{ +public: + DECLARE_PTR_TYPEDEFS(WaitGroup); + + virtual bool try_lock_shared() = 0; + virtual void unlock_shared() = 0; +}; + +/** + * A thread-safe wait group that can be stopped to prevent further shared locking. + * + * @ingroup base + */ +class StoppableWaitGroup : public WaitGroup +{ +public: + DECLARE_PTR_TYPEDEFS(StoppableWaitGroup); + + StoppableWaitGroup() = default; + StoppableWaitGroup(const StoppableWaitGroup&) = delete; + StoppableWaitGroup(StoppableWaitGroup&&) = delete; + StoppableWaitGroup& operator=(const StoppableWaitGroup&) = delete; + StoppableWaitGroup& operator=(StoppableWaitGroup&&) = delete; + + bool try_lock_shared() override; + void unlock_shared() override; + void Join(); + +private: + std::mutex m_Mutex; + std::condition_variable m_CV; + uint_fast32_t m_SharedLocks = 0; + bool m_Stopped = false; +}; + +} From c7cca7b460162d713dbf26a1d09219501eb52fa1 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 22 May 2025 17:34:37 +0200 Subject: [PATCH 321/415] Add a StoppableWaitGroup, and join it on #Stop(), to: ApiListener CheckerComponent ExternalCommandListener LivestatusListener --- lib/checker/checkercomponent.cpp | 1 + lib/checker/checkercomponent.hpp | 2 ++ lib/compat/externalcommandlistener.cpp | 2 ++ lib/compat/externalcommandlistener.hpp | 3 +++ lib/livestatus/livestatuslistener.cpp | 1 + lib/livestatus/livestatuslistener.hpp | 2 ++ lib/remote/apilistener.cpp | 1 + lib/remote/apilistener.hpp | 2 ++ 8 files changed, 14 insertions(+) diff --git a/lib/checker/checkercomponent.cpp b/lib/checker/checkercomponent.cpp index 06ebb7bba..db3833963 100644 --- a/lib/checker/checkercomponent.cpp +++ b/lib/checker/checkercomponent.cpp @@ -81,6 +81,7 @@ void CheckerComponent::Stop(bool runtimeRemoved) m_CV.notify_all(); } + m_WaitGroup->Join(); m_ResultTimer->Stop(true); m_Thread.join(); diff --git a/lib/checker/checkercomponent.hpp b/lib/checker/checkercomponent.hpp index 5ace7571c..edd3775e5 100644 --- a/lib/checker/checkercomponent.hpp +++ b/lib/checker/checkercomponent.hpp @@ -8,6 +8,7 @@ #include "base/configobject.hpp" #include "base/timer.hpp" #include "base/utility.hpp" +#include "base/wait-group.hpp" #include #include #include @@ -77,6 +78,7 @@ private: CheckableSet m_IdleCheckables; CheckableSet m_PendingCheckables; + StoppableWaitGroup::Ptr m_WaitGroup = new StoppableWaitGroup(); Timer::Ptr m_ResultTimer; void CheckThreadProc(); diff --git a/lib/compat/externalcommandlistener.cpp b/lib/compat/externalcommandlistener.cpp index b61813beb..5f5362db6 100644 --- a/lib/compat/externalcommandlistener.cpp +++ b/lib/compat/externalcommandlistener.cpp @@ -50,6 +50,8 @@ void ExternalCommandListener::Start(bool runtimeCreated) */ void ExternalCommandListener::Stop(bool runtimeRemoved) { + m_WaitGroup->Join(); + Log(LogInformation, "ExternalCommandListener") << "'" << GetName() << "' stopped."; diff --git a/lib/compat/externalcommandlistener.hpp b/lib/compat/externalcommandlistener.hpp index 895531f78..62d4f8134 100644 --- a/lib/compat/externalcommandlistener.hpp +++ b/lib/compat/externalcommandlistener.hpp @@ -5,6 +5,7 @@ #include "compat/externalcommandlistener-ti.hpp" #include "base/objectlock.hpp" +#include "base/wait-group.hpp" #include "base/timer.hpp" #include "base/utility.hpp" #include @@ -29,6 +30,8 @@ protected: void Stop(bool runtimeRemoved) override; private: + StoppableWaitGroup::Ptr m_WaitGroup = new StoppableWaitGroup(); + #ifndef _WIN32 std::thread m_CommandThread; diff --git a/lib/livestatus/livestatuslistener.cpp b/lib/livestatus/livestatuslistener.cpp index e44650bfe..fe524dcdb 100644 --- a/lib/livestatus/livestatuslistener.cpp +++ b/lib/livestatus/livestatuslistener.cpp @@ -112,6 +112,7 @@ void LivestatusListener::Stop(bool runtimeRemoved) << "'" << GetName() << "' stopped."; m_Listener->Close(); + m_WaitGroup->Join(); if (m_Thread.joinable()) m_Thread.join(); diff --git a/lib/livestatus/livestatuslistener.hpp b/lib/livestatus/livestatuslistener.hpp index dc739f6f1..60aaed930 100644 --- a/lib/livestatus/livestatuslistener.hpp +++ b/lib/livestatus/livestatuslistener.hpp @@ -7,6 +7,7 @@ #include "livestatus/livestatuslistener-ti.hpp" #include "livestatus/livestatusquery.hpp" #include "base/socket.hpp" +#include "base/wait-group.hpp" #include using namespace icinga; @@ -40,6 +41,7 @@ private: Socket::Ptr m_Listener; std::thread m_Thread; + StoppableWaitGroup::Ptr m_WaitGroup = new StoppableWaitGroup(); }; } diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index db024c987..8d5a04e9f 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -368,6 +368,7 @@ void ApiListener::Stop(bool runtimeDeleted) m_Timer->Stop(true); m_RenewOwnCertTimer->Stop(true); + m_WaitGroup->Join(); ObjectImpl::Stop(runtimeDeleted); Log(LogInformation, "ApiListener") diff --git a/lib/remote/apilistener.hpp b/lib/remote/apilistener.hpp index eae1fa03e..3aa4f40db 100644 --- a/lib/remote/apilistener.hpp +++ b/lib/remote/apilistener.hpp @@ -16,6 +16,7 @@ #include "base/tcpsocket.hpp" #include "base/tlsstream.hpp" #include "base/threadpool.hpp" +#include "base/wait-group.hpp" #include #include #include @@ -177,6 +178,7 @@ private: Timer::Ptr m_RenewOwnCertTimer; Endpoint::Ptr m_LocalEndpoint; + StoppableWaitGroup::Ptr m_WaitGroup = new StoppableWaitGroup(); static ApiListener::Ptr m_Instance; static std::atomic m_UpdatedObjectAuthority; From f4691dd05400465e17d9c465dc022005d9aa4376 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 22 May 2025 17:56:16 +0200 Subject: [PATCH 322/415] Require to pass WaitGroup::Ptr to several methods Namely: Checkable#ProcessCheckResult() ClusterCheckTask::ScriptFunc() ClusterZoneCheckTask::ScriptFunc() DummyCheckTask::ScriptFunc() ExceptionCheckTask::ScriptFunc() IcingaCheckTask::ScriptFunc() IfwApiCheckTask::ScriptFunc() NullCheckTask::ScriptFunc() PluginCheckTask::ScriptFunc() RandomCheckTask::ScriptFunc() SleepCheckTask::ScriptFunc() IdoCheckTask::ScriptFunc() IcingadbCheck::ScriptFunc() CheckCommand#Execute() Checkable#ExecuteCheck() ClusterEvents::ExecuteCheckFromQueue() ExternalCommandProcessor::Process*CheckResult() ExternalCommandCallback ExternalCommandProcessor::Execute() ExternalCommandProcessor::ExecuteFromFile() ExternalCommandProcessor::ProcessFile() LivestatusQuery#ExecuteCommandHelper() LivestatusQuery#Execute() --- lib/checker/checkercomponent.cpp | 4 +- lib/compat/externalcommandlistener.cpp | 2 +- lib/db_ido/idochecktask.cpp | 26 +++--- lib/db_ido/idochecktask.hpp | 2 +- lib/icinga/apiactions.cpp | 5 +- lib/icinga/checkable-check.cpp | 14 +-- lib/icinga/checkable-script.cpp | 5 +- lib/icinga/checkable.hpp | 9 +- lib/icinga/checkcommand.cpp | 3 +- lib/icinga/checkcommand.hpp | 1 + lib/icinga/clusterevents-check.cpp | 2 +- lib/icinga/clusterevents.cpp | 4 +- lib/icinga/externalcommandprocessor.cpp | 34 +++++--- lib/icinga/externalcommandprocessor.hpp | 17 ++-- lib/icingadb/icingadbchecktask.cpp | 20 ++--- lib/icingadb/icingadbchecktask.hpp | 2 +- lib/livestatus/livestatuslistener.cpp | 2 +- lib/livestatus/livestatusquery.cpp | 8 +- lib/livestatus/livestatusquery.hpp | 5 +- lib/methods/clusterchecktask.cpp | 8 +- lib/methods/clusterchecktask.hpp | 2 +- lib/methods/clusterzonechecktask.cpp | 12 +-- lib/methods/clusterzonechecktask.hpp | 2 +- lib/methods/dummychecktask.cpp | 6 +- lib/methods/dummychecktask.hpp | 2 +- lib/methods/exceptionchecktask.cpp | 4 +- lib/methods/exceptionchecktask.hpp | 2 +- lib/methods/icingachecktask.cpp | 6 +- lib/methods/icingachecktask.hpp | 2 +- lib/methods/ifwapichecktask.cpp | 8 +- lib/methods/ifwapichecktask.hpp | 2 +- lib/methods/nullchecktask.cpp | 6 +- lib/methods/nullchecktask.hpp | 2 +- lib/methods/pluginchecktask.cpp | 13 +-- lib/methods/pluginchecktask.hpp | 6 +- lib/methods/randomchecktask.cpp | 6 +- lib/methods/randomchecktask.hpp | 2 +- lib/methods/sleepchecktask.cpp | 6 +- lib/methods/sleepchecktask.hpp | 2 +- lib/remote/apilistener.hpp | 5 ++ test/icinga-checkable-flapping.cpp | 76 ++++++++-------- test/icinga-checkresult.cpp | 110 ++++++++++++------------ test/livestatus.cpp | 2 +- 43 files changed, 243 insertions(+), 214 deletions(-) diff --git a/lib/checker/checkercomponent.cpp b/lib/checker/checkercomponent.cpp index db3833963..1ebdf5fa1 100644 --- a/lib/checker/checkercomponent.cpp +++ b/lib/checker/checkercomponent.cpp @@ -232,7 +232,7 @@ void CheckerComponent::CheckThreadProc() void CheckerComponent::ExecuteCheckHelper(const Checkable::Ptr& checkable) { try { - checkable->ExecuteCheck(); + checkable->ExecuteCheck(m_WaitGroup); } catch (const std::exception& ex) { CheckResult::Ptr cr = new CheckResult(); cr->SetState(ServiceUnknown); @@ -246,7 +246,7 @@ void CheckerComponent::ExecuteCheckHelper(const Checkable::Ptr& checkable) cr->SetExecutionStart(now); cr->SetExecutionEnd(now); - checkable->ProcessCheckResult(cr); + checkable->ProcessCheckResult(cr, m_WaitGroup); Log(LogCritical, "checker", output); } diff --git a/lib/compat/externalcommandlistener.cpp b/lib/compat/externalcommandlistener.cpp index 5f5362db6..6ca4e3c09 100644 --- a/lib/compat/externalcommandlistener.cpp +++ b/lib/compat/externalcommandlistener.cpp @@ -138,7 +138,7 @@ void ExternalCommandListener::CommandPipeThread(const String& commandPath) Log(LogInformation, "ExternalCommandListener") << "Executing external command: " << command; - ExternalCommandProcessor::Execute(command); + ExternalCommandProcessor::Execute(m_WaitGroup, command); } catch (const std::exception& ex) { Log(LogWarning, "ExternalCommandListener") << "External command failed: " << DiagnosticInformation(ex, false); diff --git a/lib/db_ido/idochecktask.cpp b/lib/db_ido/idochecktask.cpp index 3b5856a65..d3b93e37b 100644 --- a/lib/db_ido/idochecktask.cpp +++ b/lib/db_ido/idochecktask.cpp @@ -16,11 +16,11 @@ using namespace icinga; -REGISTER_FUNCTION_NONCONST(Internal, IdoCheck, &IdoCheckTask::ScriptFunc, "checkable:cr:resolvedMacros:useResolvedMacros"); +REGISTER_FUNCTION_NONCONST(Internal, IdoCheck, &IdoCheckTask::ScriptFunc, "checkable:cr:producer:resolvedMacros:useResolvedMacros"); static void ReportIdoCheck( - const Checkable::Ptr& checkable, const CheckCommand::Ptr& commandObj, - const CheckResult::Ptr& cr, String output, ServiceState state = ServiceUnknown + const Checkable::Ptr& checkable, const CheckCommand::Ptr& commandObj, const CheckResult::Ptr& cr, + const WaitGroup::Ptr& producer, String output, ServiceState state = ServiceUnknown ) { if (Checkable::ExecuteCommandProcessFinishedHandler) { @@ -36,12 +36,12 @@ static void ReportIdoCheck( } else { cr->SetState(state); cr->SetOutput(output); - checkable->ProcessCheckResult(cr); + checkable->ProcessCheckResult(cr, producer); } } void IdoCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) { ServiceState state; CheckCommand::Ptr commandObj = CheckCommand::ExecuteOverride ? CheckCommand::ExecuteOverride : checkable->GetCheckCommand(); @@ -88,19 +88,19 @@ void IdoCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResult return; if (idoType.IsEmpty()) { - ReportIdoCheck(checkable, commandObj, cr, "Attribute 'ido_type' must be set."); + ReportIdoCheck(checkable, commandObj, cr, producer, "Attribute 'ido_type' must be set."); return; } if (idoName.IsEmpty()) { - ReportIdoCheck(checkable, commandObj, cr, "Attribute 'ido_name' must be set."); + ReportIdoCheck(checkable, commandObj, cr, producer, "Attribute 'ido_name' must be set."); return; } Type::Ptr type = Type::GetByName(idoType); if (!type || !DbConnection::TypeInstance->IsAssignableFrom(type)) { - ReportIdoCheck(checkable, commandObj, cr, "DB IDO type '" + idoType + "' is invalid."); + ReportIdoCheck(checkable, commandObj, cr, producer, "DB IDO type '" + idoType + "' is invalid."); return; } @@ -110,14 +110,14 @@ void IdoCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResult DbConnection::Ptr conn = static_pointer_cast(dtype->GetObject(idoName)); if (!conn) { - ReportIdoCheck(checkable, commandObj, cr, "DB IDO connection '" + idoName + "' does not exist."); + ReportIdoCheck(checkable, commandObj, cr, producer, "DB IDO connection '" + idoName + "' does not exist."); return; } double qps = conn->GetQueryCount(60) / 60.0; if (conn->IsPaused()) { - ReportIdoCheck(checkable, commandObj, cr, "DB IDO connection is temporarily disabled on this cluster instance.", ServiceOK); + ReportIdoCheck(checkable, commandObj, cr, producer, "DB IDO connection is temporarily disabled on this cluster instance.", ServiceOK); return; } @@ -125,10 +125,10 @@ void IdoCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResult if (!conn->GetConnected()) { if (conn->GetShouldConnect()) { - ReportIdoCheck(checkable, commandObj, cr, "Could not connect to the database server.", ServiceCritical); + ReportIdoCheck(checkable, commandObj, cr, producer, "Could not connect to the database server.", ServiceCritical); } else { ReportIdoCheck( - checkable, commandObj, cr, + checkable, commandObj, cr, producer, "Not currently enabled: Another cluster instance is responsible for the IDO database.", ServiceOK ); } @@ -193,5 +193,5 @@ void IdoCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResult { new PerfdataValue("pending_queries", pendingQueries, false, "", pendingQueriesWarning, pendingQueriesCritical) } })); - ReportIdoCheck(checkable, commandObj, cr, msgbuf.str(), state); + ReportIdoCheck(checkable, commandObj, cr, producer, msgbuf.str(), state); } diff --git a/lib/db_ido/idochecktask.hpp b/lib/db_ido/idochecktask.hpp index 5868c3867..1377d6765 100644 --- a/lib/db_ido/idochecktask.hpp +++ b/lib/db_ido/idochecktask.hpp @@ -18,7 +18,7 @@ class IdoCheckTask { public: static void ScriptFunc(const Checkable::Ptr& service, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); private: IdoCheckTask(); diff --git a/lib/icinga/apiactions.cpp b/lib/icinga/apiactions.cpp index 0f7e5cff1..3a30993fb 100644 --- a/lib/icinga/apiactions.cpp +++ b/lib/icinga/apiactions.cpp @@ -126,7 +126,8 @@ Dictionary::Ptr ApiActions::ProcessCheckResult(const ConfigObject::Ptr& object, if (params->Contains("ttl")) cr->SetTtl(HttpUtility::GetLastParameter(params, "ttl")); - Result result = checkable->ProcessCheckResult(cr); + Result result = checkable->ProcessCheckResult(cr, ApiListener::GetInstance()->GetWaitGroup()); + switch (result) { case Result::Ok: return ApiActions::CreateResult(200, "Successfully processed check result for object '" + checkable->GetName() + "'."); @@ -787,7 +788,7 @@ Dictionary::Ptr ApiActions::ExecuteCommand(const ConfigObject::Ptr& object, cons Defer resetCheckCommandOverride([]() { CheckCommand::ExecuteOverride = nullptr; }); - cmd->Execute(checkable, cr, execMacros, false); + cmd->Execute(checkable, cr, listener->GetWaitGroup(), execMacros, false); } } else if (command_type == "EventCommand") { EventCommand::Ptr cmd = GetSingleObjectByNameUsingPermissions(EventCommand::GetTypeName(), resolved_command, ActionsHandler::AuthenticatedApiUser); diff --git a/lib/icinga/checkable-check.cpp b/lib/icinga/checkable-check.cpp index 868921dc9..b2b5ca45e 100644 --- a/lib/icinga/checkable-check.cpp +++ b/lib/icinga/checkable-check.cpp @@ -96,7 +96,7 @@ double Checkable::GetLastCheck() const return schedule_end; } -Checkable::ProcessingResult Checkable::ProcessCheckResult(const CheckResult::Ptr& cr, const MessageOrigin::Ptr& origin) +Checkable::ProcessingResult Checkable::ProcessCheckResult(const CheckResult::Ptr& cr, const WaitGroup::Ptr& producer, const MessageOrigin::Ptr& origin) { using Result = Checkable::ProcessingResult; @@ -544,7 +544,7 @@ Checkable::ProcessingResult Checkable::ProcessCheckResult(const CheckResult::Ptr return Result::Ok; } -void Checkable::ExecuteRemoteCheck(const Dictionary::Ptr& resolvedMacros) +void Checkable::ExecuteRemoteCheck(const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros) { CONTEXT("Executing remote check for object '" << GetName() << "'"); @@ -555,10 +555,10 @@ void Checkable::ExecuteRemoteCheck(const Dictionary::Ptr& resolvedMacros) cr->SetScheduleStart(scheduled_start); cr->SetExecutionStart(before_check); - GetCheckCommand()->Execute(this, cr, resolvedMacros, true); + GetCheckCommand()->Execute(this, cr, producer, resolvedMacros, true); } -void Checkable::ExecuteCheck() +void Checkable::ExecuteCheck(const WaitGroup::Ptr& producer) { CONTEXT("Executing check for object '" << GetName() << "'"); @@ -599,10 +599,10 @@ void Checkable::ExecuteCheck() bool local = !endpoint || endpoint == Endpoint::GetLocalEndpoint(); if (local) { - GetCheckCommand()->Execute(this, cr, nullptr, false); + GetCheckCommand()->Execute(this, cr, producer, nullptr, false); } else { Dictionary::Ptr macros = new Dictionary(); - GetCheckCommand()->Execute(this, cr, macros, false); + GetCheckCommand()->Execute(this, cr, producer, macros, false); if (endpoint->GetConnected()) { /* perform check on remote endpoint */ @@ -663,7 +663,7 @@ void Checkable::ExecuteCheck() cr->SetOutput(output); - ProcessCheckResult(cr); + ProcessCheckResult(cr, producer); } { diff --git a/lib/icinga/checkable-script.cpp b/lib/icinga/checkable-script.cpp index 764a9f851..a455440ca 100644 --- a/lib/icinga/checkable-script.cpp +++ b/lib/icinga/checkable-script.cpp @@ -6,6 +6,7 @@ #include "base/function.hpp" #include "base/functionwrapper.hpp" #include "base/scriptframe.hpp" +#include "remote/apilistener.hpp" using namespace icinga; @@ -16,7 +17,9 @@ static void CheckableProcessCheckResult(const CheckResult::Ptr& cr) REQUIRE_NOT_NULL(self); if (cr) { - self->ProcessCheckResult(cr); + auto api (ApiListener::GetInstance()); + + self->ProcessCheckResult(cr, api ? api->GetWaitGroup() : new StoppableWaitGroup()); } } diff --git a/lib/icinga/checkable.hpp b/lib/icinga/checkable.hpp index da6be5630..310923ec8 100644 --- a/lib/icinga/checkable.hpp +++ b/lib/icinga/checkable.hpp @@ -12,6 +12,7 @@ #include "icinga/notification.hpp" #include "icinga/comment.hpp" #include "icinga/downtime.hpp" +#include "base/wait-group.hpp" #include "remote/endpoint.hpp" #include "remote/messageorigin.hpp" #include @@ -114,15 +115,17 @@ public: static void UpdateStatistics(const CheckResult::Ptr& cr, CheckableType type); - void ExecuteRemoteCheck(const Dictionary::Ptr& resolvedMacros = nullptr); - void ExecuteCheck(); + void ExecuteRemoteCheck(const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros = nullptr); + void ExecuteCheck(const WaitGroup::Ptr& producer); + enum class ProcessingResult { Ok, CheckableInactive, NewerCheckResultPresent, }; - ProcessingResult ProcessCheckResult(const CheckResult::Ptr& cr, const MessageOrigin::Ptr& origin = nullptr); + + ProcessingResult ProcessCheckResult(const CheckResult::Ptr& cr, const WaitGroup::Ptr& producer, const MessageOrigin::Ptr& origin = nullptr); Endpoint::Ptr GetCommandEndpoint() const; diff --git a/lib/icinga/checkcommand.cpp b/lib/icinga/checkcommand.cpp index fb8032a19..e1aed1718 100644 --- a/lib/icinga/checkcommand.cpp +++ b/lib/icinga/checkcommand.cpp @@ -11,11 +11,12 @@ REGISTER_TYPE(CheckCommand); thread_local CheckCommand::Ptr CheckCommand::ExecuteOverride; void CheckCommand::Execute(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) { GetExecute()->Invoke({ checkable, cr, + producer, resolvedMacros, useResolvedMacros }); diff --git a/lib/icinga/checkcommand.hpp b/lib/icinga/checkcommand.hpp index c654cf93e..1ed3b9ae7 100644 --- a/lib/icinga/checkcommand.hpp +++ b/lib/icinga/checkcommand.hpp @@ -23,6 +23,7 @@ public: static thread_local CheckCommand::Ptr ExecuteOverride; void Execute(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros = nullptr, bool useResolvedMacros = false); }; diff --git a/lib/icinga/clusterevents-check.cpp b/lib/icinga/clusterevents-check.cpp index 40325b4c0..205c4c79d 100644 --- a/lib/icinga/clusterevents-check.cpp +++ b/lib/icinga/clusterevents-check.cpp @@ -279,7 +279,7 @@ void ClusterEvents::ExecuteCheckFromQueue(const MessageOrigin::Ptr& origin, cons if (command_type == "check_command") { try { - host->ExecuteRemoteCheck(macros); + host->ExecuteRemoteCheck(ApiListener::GetInstance()->GetWaitGroup(), macros); } catch (const std::exception& ex) { String output = "Exception occurred while checking '" + host->GetName() + "': " + DiagnosticInformation(ex); ServiceState state = ServiceUnknown; diff --git a/lib/icinga/clusterevents.cpp b/lib/icinga/clusterevents.cpp index b49d2071d..a45ac458b 100644 --- a/lib/icinga/clusterevents.cpp +++ b/lib/icinga/clusterevents.cpp @@ -176,9 +176,9 @@ Value ClusterEvents::CheckResultAPIHandler(const MessageOrigin::Ptr& origin, con } if (!checkable->IsPaused() && Zone::GetLocalZone() == checkable->GetZone() && endpoint == checkable->GetCommandEndpoint()) - checkable->ProcessCheckResult(cr); + checkable->ProcessCheckResult(cr, ApiListener::GetInstance()->GetWaitGroup()); else - checkable->ProcessCheckResult(cr, origin); + checkable->ProcessCheckResult(cr, ApiListener::GetInstance()->GetWaitGroup(), origin); return Empty; } diff --git a/lib/icinga/externalcommandprocessor.cpp b/lib/icinga/externalcommandprocessor.cpp index 0560947aa..fbc0432a4 100644 --- a/lib/icinga/externalcommandprocessor.cpp +++ b/lib/icinga/externalcommandprocessor.cpp @@ -27,7 +27,7 @@ using namespace icinga; boost::signals2::signal&)> ExternalCommandProcessor::OnNewExternalCommand; -void ExternalCommandProcessor::Execute(const String& line) +void ExternalCommandProcessor::Execute(const WaitGroup::Ptr& producer, const String& line) { if (line.IsEmpty()) return; @@ -54,10 +54,10 @@ void ExternalCommandProcessor::Execute(const String& line) BOOST_THROW_EXCEPTION(std::invalid_argument("Missing arguments in command: " + line)); std::vector argvExtra(argv.begin() + 1, argv.end()); - Execute(ts, argv[0], argvExtra); + Execute(producer, ts, argv[0], argvExtra); } -void ExternalCommandProcessor::Execute(double time, const String& command, const std::vector& arguments) +void ExternalCommandProcessor::Execute(const WaitGroup::Ptr& producer, double time, const String& command, const std::vector& arguments) { ExternalCommandInfo eci; @@ -102,7 +102,7 @@ void ExternalCommandProcessor::Execute(double time, const String& command, const OnNewExternalCommand(time, command, realArguments); - eci.Callback(time, realArguments); + eci.Callback(producer, time, realArguments); } void ExternalCommandProcessor::RegisterCommand(const String& command, const ExternalCommandCallback& callback, size_t minArgs, size_t maxArgs) @@ -115,6 +115,16 @@ void ExternalCommandProcessor::RegisterCommand(const String& command, const Exte GetCommands()[command] = eci; } +void ExternalCommandProcessor::RegisterCommand(const String& command, const ExternalCommandCallbackLite& callback, size_t minArgs, size_t maxArgs) +{ + RegisterCommand( + command, + [callback](const WaitGroup::Ptr&, double time, const std::vector& args) { callback(time, args); }, + minArgs, + maxArgs + ); +} + void ExternalCommandProcessor::RegisterCommands() { RegisterCommand("PROCESS_HOST_CHECK_RESULT", &ExternalCommandProcessor::ProcessHostCheckResult, 3); @@ -237,7 +247,7 @@ void ExternalCommandProcessor::RegisterCommands() RegisterCommand("DISABLE_SERVICEGROUP_SVC_NOTIFICATIONS", &ExternalCommandProcessor::DisableServicegroupSvcNotifications, 1); } -void ExternalCommandProcessor::ExecuteFromFile(const String& line, std::deque< std::vector >& file_queue) +void ExternalCommandProcessor::ExecuteFromFile(const WaitGroup::Ptr& producer, const String& line, std::deque>& file_queue) { if (line.IsEmpty()) return; @@ -270,11 +280,11 @@ void ExternalCommandProcessor::ExecuteFromFile(const String& line, std::deque< s << "Enqueing external command file " << argvExtra[0]; file_queue.push_back(argvExtra); } else { - Execute(ts, argv[0], argvExtra); + Execute(producer, ts, argv[0], argvExtra); } } -void ExternalCommandProcessor::ProcessHostCheckResult(double time, const std::vector& arguments) +void ExternalCommandProcessor::ProcessHostCheckResult(const WaitGroup::Ptr& producer, double time, const std::vector& arguments) { Host::Ptr host = Host::GetByName(arguments[0]); @@ -318,10 +328,10 @@ void ExternalCommandProcessor::ProcessHostCheckResult(double time, const std::ve Log(LogNotice, "ExternalCommandProcessor") << "Processing passive check result for host '" << arguments[0] << "'"; - host->ProcessCheckResult(result); + host->ProcessCheckResult(result, producer); } -void ExternalCommandProcessor::ProcessServiceCheckResult(double time, const std::vector& arguments) +void ExternalCommandProcessor::ProcessServiceCheckResult(const WaitGroup::Ptr& producer, double time, const std::vector& arguments) { Service::Ptr service = Service::GetByNamePair(arguments[0], arguments[1]); @@ -356,7 +366,7 @@ void ExternalCommandProcessor::ProcessServiceCheckResult(double time, const std: Log(LogNotice, "ExternalCommandProcessor") << "Processing passive check result for service '" << arguments[1] << "'"; - service->ProcessCheckResult(result); + service->ProcessCheckResult(result, producer); } void ExternalCommandProcessor::ScheduleHostCheck(double, const std::vector& arguments) @@ -921,7 +931,7 @@ void ExternalCommandProcessor::DisableHostgroupPassiveSvcChecks(double, const st } } -void ExternalCommandProcessor::ProcessFile(double, const std::vector& arguments) +void ExternalCommandProcessor::ProcessFile(const WaitGroup::Ptr& producer, double, const std::vector& arguments) { std::deque< std::vector > file_queue; file_queue.push_back(arguments); @@ -946,7 +956,7 @@ void ExternalCommandProcessor::ProcessFile(double, const std::vector& ar Log(LogNotice, "compat") << "Executing external command: " << line; - ExecuteFromFile(line, file_queue); + ExecuteFromFile(producer, line, file_queue); } catch (const std::exception& ex) { Log(LogWarning, "ExternalCommandProcessor") << "External command failed: " << DiagnosticInformation(ex); diff --git a/lib/icinga/externalcommandprocessor.hpp b/lib/icinga/externalcommandprocessor.hpp index a7c5a3068..38db883d2 100644 --- a/lib/icinga/externalcommandprocessor.hpp +++ b/lib/icinga/externalcommandprocessor.hpp @@ -5,6 +5,7 @@ #include "icinga/i2-icinga.hpp" #include "icinga/command.hpp" +#include "base/wait-group.hpp" #include "base/string.hpp" #include #include @@ -12,7 +13,8 @@ namespace icinga { -typedef std::function& arguments)> ExternalCommandCallback; +typedef std::function& arguments)> ExternalCommandCallback; +typedef std::function& arguments)> ExternalCommandCallbackLite; struct ExternalCommandInfo { @@ -23,18 +25,18 @@ struct ExternalCommandInfo class ExternalCommandProcessor { public: - static void Execute(const String& line); - static void Execute(double time, const String& command, const std::vector& arguments); + static void Execute(const WaitGroup::Ptr& producer, const String& line); + static void Execute(const WaitGroup::Ptr& producer, double time, const String& command, const std::vector& arguments); static boost::signals2::signal&)> OnNewExternalCommand; private: ExternalCommandProcessor(); - static void ExecuteFromFile(const String& line, std::deque< std::vector >& file_queue); + static void ExecuteFromFile(const WaitGroup::Ptr& producer, const String& line, std::deque>& file_queue); - static void ProcessHostCheckResult(double time, const std::vector& arguments); - static void ProcessServiceCheckResult(double time, const std::vector& arguments); + static void ProcessHostCheckResult(const WaitGroup::Ptr& producer, double time, const std::vector& arguments); + static void ProcessServiceCheckResult(const WaitGroup::Ptr& producer, double time, const std::vector& arguments); static void ScheduleHostCheck(double time, const std::vector& arguments); static void ScheduleForcedHostCheck(double time, const std::vector& arguments); static void ScheduleSvcCheck(double time, const std::vector& arguments); @@ -67,7 +69,7 @@ private: static void DisableServicegroupPassiveSvcChecks(double time, const std::vector& arguments); static void EnableHostgroupPassiveSvcChecks(double time, const std::vector& arguments); static void DisableHostgroupPassiveSvcChecks(double time, const std::vector& arguments); - static void ProcessFile(double time, const std::vector& arguments); + static void ProcessFile(const WaitGroup::Ptr& producer, double time, const std::vector& arguments); static void ScheduleSvcDowntime(double time, const std::vector& arguments); static void DelSvcDowntime(double time, const std::vector& arguments); static void ScheduleHostDowntime(double time, const std::vector& arguments); @@ -157,6 +159,7 @@ private: static void ChangeCustomCommandVarInternal(const Command::Ptr& command, const String& name, const Value& value); static void RegisterCommand(const String& command, const ExternalCommandCallback& callback, size_t minArgs = 0, size_t maxArgs = UINT_MAX); + static void RegisterCommand(const String& command, const ExternalCommandCallbackLite& callback, size_t minArgs = 0, size_t maxArgs = UINT_MAX); static void RegisterCommands(); static std::mutex& GetMutex(); diff --git a/lib/icingadb/icingadbchecktask.cpp b/lib/icingadb/icingadbchecktask.cpp index cee93cd33..54e76e894 100644 --- a/lib/icingadb/icingadbchecktask.cpp +++ b/lib/icingadb/icingadbchecktask.cpp @@ -13,11 +13,11 @@ using namespace icinga; -REGISTER_FUNCTION_NONCONST(Internal, IcingadbCheck, &IcingadbCheckTask::ScriptFunc, "checkable:cr:resolvedMacros:useResolvedMacros"); +REGISTER_FUNCTION_NONCONST(Internal, IcingadbCheck, &IcingadbCheckTask::ScriptFunc, "checkable:cr:producer:resolvedMacros:useResolvedMacros"); static void ReportIcingadbCheck( const Checkable::Ptr& checkable, const CheckCommand::Ptr& commandObj, - const CheckResult::Ptr& cr, String output, ServiceState state) + const CheckResult::Ptr& cr, const WaitGroup::Ptr& producer, String output, ServiceState state) { if (Checkable::ExecuteCommandProcessFinishedHandler) { double now = Utility::GetTime(); @@ -32,7 +32,7 @@ static void ReportIcingadbCheck( } else { cr->SetState(state); cr->SetOutput(output); - checkable->ProcessCheckResult(cr); + checkable->ProcessCheckResult(cr, producer); } } @@ -43,7 +43,7 @@ double GetXMessageTs(const Array::Ptr& xMessage) } void IcingadbCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) { CheckCommand::Ptr commandObj = CheckCommand::ExecuteOverride ? CheckCommand::ExecuteOverride : checkable->GetCheckCommand(); @@ -87,21 +87,21 @@ void IcingadbCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckR return; if (icingadbName.IsEmpty()) { - ReportIcingadbCheck(checkable, commandObj, cr, "Icinga DB UNKNOWN: Attribute 'icingadb_name' must be set.", ServiceUnknown); + ReportIcingadbCheck(checkable, commandObj, cr, producer, "Icinga DB UNKNOWN: Attribute 'icingadb_name' must be set.", ServiceUnknown); return; } auto conn (IcingaDB::GetByName(icingadbName)); if (!conn) { - ReportIcingadbCheck(checkable, commandObj, cr, "Icinga DB UNKNOWN: Icinga DB connection '" + icingadbName + "' does not exist.", ServiceUnknown); + ReportIcingadbCheck(checkable, commandObj, cr, producer, "Icinga DB UNKNOWN: Icinga DB connection '" + icingadbName + "' does not exist.", ServiceUnknown); return; } auto redis (conn->GetConnection()); if (!redis || !redis->GetConnected()) { - ReportIcingadbCheck(checkable, commandObj, cr, "Icinga DB CRITICAL: Not connected to Redis.", ServiceCritical); + ReportIcingadbCheck(checkable, commandObj, cr, producer, "Icinga DB CRITICAL: Not connected to Redis.", ServiceCritical); return; } @@ -136,7 +136,7 @@ void IcingadbCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckR xReadHistoryBacklog = std::move(replies.at(4)); } catch (const std::exception& ex) { ReportIcingadbCheck( - checkable, commandObj, cr, + checkable, commandObj, cr, producer, String("Icinga DB CRITICAL: Could not query Redis: ") + ex.what(), ServiceCritical ); return; @@ -144,7 +144,7 @@ void IcingadbCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckR if (!xReadHeartbeat) { ReportIcingadbCheck( - checkable, commandObj, cr, + checkable, commandObj, cr, producer, "Icinga DB CRITICAL: The Icinga DB daemon seems to have never run. (Missing heartbeat)", ServiceCritical ); @@ -511,5 +511,5 @@ void IcingadbCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckR } cr->SetPerformanceData(perfdata); - ReportIcingadbCheck(checkable, commandObj, cr, msgbuf.str(), state); + ReportIcingadbCheck(checkable, commandObj, cr, producer, msgbuf.str(), state); } diff --git a/lib/icingadb/icingadbchecktask.hpp b/lib/icingadb/icingadbchecktask.hpp index ba7d61b1e..5fffff4f1 100644 --- a/lib/icingadb/icingadbchecktask.hpp +++ b/lib/icingadb/icingadbchecktask.hpp @@ -18,7 +18,7 @@ class IcingadbCheckTask { public: static void ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); private: IcingadbCheckTask(); diff --git a/lib/livestatus/livestatuslistener.cpp b/lib/livestatus/livestatuslistener.cpp index fe524dcdb..9f48bcdbe 100644 --- a/lib/livestatus/livestatuslistener.cpp +++ b/lib/livestatus/livestatuslistener.cpp @@ -192,7 +192,7 @@ void LivestatusListener::ClientHandler(const Socket::Ptr& client) break; LivestatusQuery::Ptr query = new LivestatusQuery(lines, GetCompatLogPath()); - if (!query->Execute(stream)) + if (!query->Execute(m_WaitGroup, stream)) break; } diff --git a/lib/livestatus/livestatusquery.cpp b/lib/livestatus/livestatusquery.cpp index 0f9b3dac9..c152eaf27 100644 --- a/lib/livestatus/livestatusquery.cpp +++ b/lib/livestatus/livestatusquery.cpp @@ -570,7 +570,7 @@ void LivestatusQuery::ExecuteGetHelper(const Stream::Ptr& stream) SendResponse(stream, LivestatusErrorOK, result.str()); } -void LivestatusQuery::ExecuteCommandHelper(const Stream::Ptr& stream) +void LivestatusQuery::ExecuteCommandHelper(const WaitGroup::Ptr& producer, const Stream::Ptr& stream) { { std::unique_lock lock(l_QueryMutex); @@ -580,7 +580,7 @@ void LivestatusQuery::ExecuteCommandHelper(const Stream::Ptr& stream) Log(LogNotice, "LivestatusQuery") << "Executing command: " << m_Command; - ExternalCommandProcessor::Execute(m_Command); + ExternalCommandProcessor::Execute(producer, m_Command); SendResponse(stream, LivestatusErrorOK, ""); } @@ -621,7 +621,7 @@ void LivestatusQuery::PrintFixed16(const Stream::Ptr& stream, int code, const St } } -bool LivestatusQuery::Execute(const Stream::Ptr& stream) +bool LivestatusQuery::Execute(const WaitGroup::Ptr& producer, const Stream::Ptr& stream) { try { Log(LogNotice, "LivestatusQuery") @@ -630,7 +630,7 @@ bool LivestatusQuery::Execute(const Stream::Ptr& stream) if (m_Verb == "GET") ExecuteGetHelper(stream); else if (m_Verb == "COMMAND") - ExecuteCommandHelper(stream); + ExecuteCommandHelper(producer, stream); else if (m_Verb == "ERROR") ExecuteErrorHelper(stream); else diff --git a/lib/livestatus/livestatusquery.hpp b/lib/livestatus/livestatusquery.hpp index 910cc162e..838a9a8f5 100644 --- a/lib/livestatus/livestatusquery.hpp +++ b/lib/livestatus/livestatusquery.hpp @@ -5,6 +5,7 @@ #include "livestatus/filter.hpp" #include "livestatus/aggregator.hpp" +#include "base/wait-group.hpp" #include "base/object.hpp" #include "base/array.hpp" #include "base/stream.hpp" @@ -33,7 +34,7 @@ public: LivestatusQuery(const std::vector& lines, const String& compat_log_path); - bool Execute(const Stream::Ptr& stream); + bool Execute(const WaitGroup::Ptr& producer, const Stream::Ptr& stream); static int GetExternalCommands(); @@ -76,7 +77,7 @@ private: static String QuoteStringPython(const String& str); void ExecuteGetHelper(const Stream::Ptr& stream); - void ExecuteCommandHelper(const Stream::Ptr& stream); + void ExecuteCommandHelper(const WaitGroup::Ptr& producer, const Stream::Ptr& stream); void ExecuteErrorHelper(const Stream::Ptr& stream); void SendResponse(const Stream::Ptr& stream, int code, const String& data); diff --git a/lib/methods/clusterchecktask.cpp b/lib/methods/clusterchecktask.cpp index 6ce28cac4..c629225c3 100644 --- a/lib/methods/clusterchecktask.cpp +++ b/lib/methods/clusterchecktask.cpp @@ -17,10 +17,10 @@ using namespace icinga; -REGISTER_FUNCTION_NONCONST(Internal, ClusterCheck, &ClusterCheckTask::ScriptFunc, "checkable:cr:resolvedMacros:useResolvedMacros"); +REGISTER_FUNCTION_NONCONST(Internal, ClusterCheck, &ClusterCheckTask::ScriptFunc, "checkable:cr:producer:resolvedMacros:useResolvedMacros"); void ClusterCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) { REQUIRE_NOT_NULL(checkable); REQUIRE_NOT_NULL(cr); @@ -47,7 +47,7 @@ void ClusterCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRe } else { cr->SetOutput(output); cr->SetState(ServiceUnknown); - checkable->ProcessCheckResult(cr); + checkable->ProcessCheckResult(cr, producer); } return; @@ -92,7 +92,7 @@ void ClusterCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRe cr->SetState(state); cr->SetOutput(output); - checkable->ProcessCheckResult(cr); + checkable->ProcessCheckResult(cr, producer); } } diff --git a/lib/methods/clusterchecktask.hpp b/lib/methods/clusterchecktask.hpp index 16ee8a53c..39e544c1b 100644 --- a/lib/methods/clusterchecktask.hpp +++ b/lib/methods/clusterchecktask.hpp @@ -17,7 +17,7 @@ class ClusterCheckTask { public: static void ScriptFunc(const Checkable::Ptr& service, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); private: ClusterCheckTask(); diff --git a/lib/methods/clusterzonechecktask.cpp b/lib/methods/clusterzonechecktask.cpp index fd52534c3..d7c99138c 100644 --- a/lib/methods/clusterzonechecktask.cpp +++ b/lib/methods/clusterzonechecktask.cpp @@ -12,10 +12,10 @@ using namespace icinga; -REGISTER_FUNCTION_NONCONST(Internal, ClusterZoneCheck, &ClusterZoneCheckTask::ScriptFunc, "checkable:cr:resolvedMacros:useResolvedMacros"); +REGISTER_FUNCTION_NONCONST(Internal, ClusterZoneCheck, &ClusterZoneCheckTask::ScriptFunc, "checkable:cr:producer:resolvedMacros:useResolvedMacros"); void ClusterZoneCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) { REQUIRE_NOT_NULL(checkable); REQUIRE_NOT_NULL(cr); @@ -42,7 +42,7 @@ void ClusterZoneCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const Che cr->SetCommand(commandName); cr->SetOutput(output); cr->SetState(state); - checkable->ProcessCheckResult(cr); + checkable->ProcessCheckResult(cr, producer); } return; @@ -97,7 +97,7 @@ void ClusterZoneCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const Che cr->SetCommand(commandName); cr->SetOutput(output); cr->SetState(state); - checkable->ProcessCheckResult(cr); + checkable->ProcessCheckResult(cr, producer); } return; @@ -123,7 +123,7 @@ void ClusterZoneCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const Che cr->SetCommand(commandName); cr->SetOutput(output); cr->SetState(state); - checkable->ProcessCheckResult(cr); + checkable->ProcessCheckResult(cr, producer); } return; } @@ -213,6 +213,6 @@ void ClusterZoneCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const Che new PerfdataValue("sum_bytes_received_per_second", bytesReceivedPerSecond) })); - checkable->ProcessCheckResult(cr); + checkable->ProcessCheckResult(cr, producer); } } diff --git a/lib/methods/clusterzonechecktask.hpp b/lib/methods/clusterzonechecktask.hpp index 2af442c4c..2bf3e1065 100644 --- a/lib/methods/clusterzonechecktask.hpp +++ b/lib/methods/clusterzonechecktask.hpp @@ -17,7 +17,7 @@ class ClusterZoneCheckTask { public: static void ScriptFunc(const Checkable::Ptr& service, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); private: ClusterZoneCheckTask(); diff --git a/lib/methods/dummychecktask.cpp b/lib/methods/dummychecktask.cpp index 905a022c3..85ef33d9a 100644 --- a/lib/methods/dummychecktask.cpp +++ b/lib/methods/dummychecktask.cpp @@ -13,10 +13,10 @@ using namespace icinga; -REGISTER_FUNCTION_NONCONST(Internal, DummyCheck, &DummyCheckTask::ScriptFunc, "checkable:cr:resolvedMacros:useResolvedMacros"); +REGISTER_FUNCTION_NONCONST(Internal, DummyCheck, &DummyCheckTask::ScriptFunc, "checkable:cr:producer:resolvedMacros:useResolvedMacros"); void DummyCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) { REQUIRE_NOT_NULL(checkable); REQUIRE_NOT_NULL(cr); @@ -70,6 +70,6 @@ void DummyCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResu cr->SetExecutionEnd(now); cr->SetCommand(commandName); - checkable->ProcessCheckResult(cr); + checkable->ProcessCheckResult(cr, producer); } } diff --git a/lib/methods/dummychecktask.hpp b/lib/methods/dummychecktask.hpp index 621bbfb9f..9c32a19ad 100644 --- a/lib/methods/dummychecktask.hpp +++ b/lib/methods/dummychecktask.hpp @@ -19,7 +19,7 @@ class DummyCheckTask { public: static void ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); private: DummyCheckTask(); diff --git a/lib/methods/exceptionchecktask.cpp b/lib/methods/exceptionchecktask.cpp index 47707f26f..645a751bd 100644 --- a/lib/methods/exceptionchecktask.cpp +++ b/lib/methods/exceptionchecktask.cpp @@ -12,10 +12,10 @@ using namespace icinga; -REGISTER_FUNCTION_NONCONST(Internal, ExceptionCheck, &ExceptionCheckTask::ScriptFunc, "checkable:cr:resolvedMacros:useResolvedMacros"); +REGISTER_FUNCTION_NONCONST(Internal, ExceptionCheck, &ExceptionCheckTask::ScriptFunc, "checkable:cr:producer:resolvedMacros:useResolvedMacros"); void ExceptionCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) + const WaitGroup::Ptr&, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) { REQUIRE_NOT_NULL(checkable); REQUIRE_NOT_NULL(cr); diff --git a/lib/methods/exceptionchecktask.hpp b/lib/methods/exceptionchecktask.hpp index 09db1045f..6d8c8c4e0 100644 --- a/lib/methods/exceptionchecktask.hpp +++ b/lib/methods/exceptionchecktask.hpp @@ -18,7 +18,7 @@ class ExceptionCheckTask { public: static void ScriptFunc(const Checkable::Ptr& service, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); private: ExceptionCheckTask(); diff --git a/lib/methods/icingachecktask.cpp b/lib/methods/icingachecktask.cpp index d3eae1f33..3a8e9fbce 100644 --- a/lib/methods/icingachecktask.cpp +++ b/lib/methods/icingachecktask.cpp @@ -17,10 +17,10 @@ using namespace icinga; -REGISTER_FUNCTION_NONCONST(Internal, IcingaCheck, &IcingaCheckTask::ScriptFunc, "checkable:cr:resolvedMacros:useResolvedMacros"); +REGISTER_FUNCTION_NONCONST(Internal, IcingaCheck, &IcingaCheckTask::ScriptFunc, "checkable:cr:producer:resolvedMacros:useResolvedMacros"); void IcingaCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) { REQUIRE_NOT_NULL(checkable); REQUIRE_NOT_NULL(cr); @@ -204,6 +204,6 @@ void IcingaCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes cr->SetOutput(output); cr->SetCommand(commandName); - checkable->ProcessCheckResult(cr); + checkable->ProcessCheckResult(cr, producer); } } diff --git a/lib/methods/icingachecktask.hpp b/lib/methods/icingachecktask.hpp index 93def628d..94e4c6e76 100644 --- a/lib/methods/icingachecktask.hpp +++ b/lib/methods/icingachecktask.hpp @@ -18,7 +18,7 @@ class IcingaCheckTask { public: static void ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); private: IcingaCheckTask(); diff --git a/lib/methods/ifwapichecktask.cpp b/lib/methods/ifwapichecktask.cpp index ce48deefc..fd3cc21c8 100644 --- a/lib/methods/ifwapichecktask.cpp +++ b/lib/methods/ifwapichecktask.cpp @@ -31,7 +31,7 @@ using namespace icinga; -REGISTER_FUNCTION_NONCONST(Internal, IfwApiCheck, &IfwApiCheckTask::ScriptFunc, "checkable:cr:resolvedMacros:useResolvedMacros"); +REGISTER_FUNCTION_NONCONST(Internal, IfwApiCheck, &IfwApiCheckTask::ScriptFunc, "checkable:cr:producer:resolvedMacros:useResolvedMacros"); static const char* GetUnderstandableError(const std::exception& ex) { @@ -194,7 +194,7 @@ static void DoIfwNetIo( } void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) { namespace asio = boost::asio; namespace http = boost::beast::http; @@ -213,7 +213,7 @@ void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes if (!(commandEndpoint->GetCapabilities() & (uint_fast64_t)ApiCapabilities::IfwApiCheckCommand)) { // Assume "ifw-api-check-command" has been imported into a check command which can also work // based on "plugin-check-command", delegate respectively and hope for the best - PluginCheckTask::ScriptFunc(checkable, cr, resolvedMacros, useResolvedMacros); + PluginCheckTask::ScriptFunc(checkable, cr, producer, resolvedMacros, useResolvedMacros); return; } } @@ -275,7 +275,7 @@ void IfwApiCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes callback(cr->GetCommand(), pr); }; } else { - reportResult = [checkable, cr]() { checkable->ProcessCheckResult(cr); }; + reportResult = [checkable, cr, producer] { checkable->ProcessCheckResult(cr, producer); }; } // Set the default check result state and exit code to unknown for the moment! diff --git a/lib/methods/ifwapichecktask.hpp b/lib/methods/ifwapichecktask.hpp index 39327336b..0c2b3bac2 100644 --- a/lib/methods/ifwapichecktask.hpp +++ b/lib/methods/ifwapichecktask.hpp @@ -18,7 +18,7 @@ class IfwApiCheckTask { public: static void ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); private: IfwApiCheckTask(); diff --git a/lib/methods/nullchecktask.cpp b/lib/methods/nullchecktask.cpp index ee660294e..07479c366 100644 --- a/lib/methods/nullchecktask.cpp +++ b/lib/methods/nullchecktask.cpp @@ -13,10 +13,10 @@ using namespace icinga; -REGISTER_FUNCTION_NONCONST(Internal, NullCheck, &NullCheckTask::ScriptFunc, "checkable:cr:resolvedMacros:useResolvedMacros"); +REGISTER_FUNCTION_NONCONST(Internal, NullCheck, &NullCheckTask::ScriptFunc, "checkable:cr:producer:resolvedMacros:useResolvedMacros"); void NullCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) { REQUIRE_NOT_NULL(checkable); REQUIRE_NOT_NULL(cr); @@ -45,6 +45,6 @@ void NullCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResul })); cr->SetState(state); - checkable->ProcessCheckResult(cr); + checkable->ProcessCheckResult(cr, producer); } } diff --git a/lib/methods/nullchecktask.hpp b/lib/methods/nullchecktask.hpp index 954cf8d61..1b991f132 100644 --- a/lib/methods/nullchecktask.hpp +++ b/lib/methods/nullchecktask.hpp @@ -19,7 +19,7 @@ class NullCheckTask { public: static void ScriptFunc(const Checkable::Ptr& service, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); private: NullCheckTask(); diff --git a/lib/methods/pluginchecktask.cpp b/lib/methods/pluginchecktask.cpp index 1a3df8105..f5600bd14 100644 --- a/lib/methods/pluginchecktask.cpp +++ b/lib/methods/pluginchecktask.cpp @@ -14,10 +14,10 @@ using namespace icinga; -REGISTER_FUNCTION_NONCONST(Internal, PluginCheck, &PluginCheckTask::ScriptFunc, "checkable:cr:resolvedMacros:useResolvedMacros"); +REGISTER_FUNCTION_NONCONST(Internal, PluginCheck, &PluginCheckTask::ScriptFunc, "checkable:cr:producer:resolvedMacros:useResolvedMacros"); void PluginCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) { REQUIRE_NOT_NULL(checkable); REQUIRE_NOT_NULL(cr); @@ -48,8 +48,8 @@ void PluginCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes if (Checkable::ExecuteCommandProcessFinishedHandler) { callback = Checkable::ExecuteCommandProcessFinishedHandler; } else { - callback = [checkable, cr](const Value& commandLine, const ProcessResult& pr) { - ProcessFinishedHandler(checkable, cr, commandLine, pr); + callback = [checkable, cr, producer](const Value& commandLine, const ProcessResult& pr) { + ProcessFinishedHandler(checkable, cr, producer, commandLine, pr); }; } @@ -62,7 +62,8 @@ void PluginCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes } } -void PluginCheckTask::ProcessFinishedHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, const Value& commandLine, const ProcessResult& pr) +void PluginCheckTask::ProcessFinishedHandler(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, + const WaitGroup::Ptr& producer, const Value& commandLine, const ProcessResult& pr) { Checkable::CurrentConcurrentChecks.fetch_sub(1); Checkable::DecreasePendingChecks(); @@ -93,5 +94,5 @@ void PluginCheckTask::ProcessFinishedHandler(const Checkable::Ptr& checkable, co cr->SetExecutionStart(pr.ExecutionStart); cr->SetExecutionEnd(pr.ExecutionEnd); - checkable->ProcessCheckResult(cr); + checkable->ProcessCheckResult(cr, producer); } diff --git a/lib/methods/pluginchecktask.hpp b/lib/methods/pluginchecktask.hpp index a4fc3a385..3491c65f9 100644 --- a/lib/methods/pluginchecktask.hpp +++ b/lib/methods/pluginchecktask.hpp @@ -19,13 +19,13 @@ class PluginCheckTask { public: static void ScriptFunc(const Checkable::Ptr& service, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); private: PluginCheckTask(); - static void ProcessFinishedHandler(const Checkable::Ptr& service, - const CheckResult::Ptr& cr, const Value& commandLine, const ProcessResult& pr); + static void ProcessFinishedHandler(const Checkable::Ptr& service, const CheckResult::Ptr& cr, + const WaitGroup::Ptr& producer, const Value& commandLine, const ProcessResult& pr); }; } diff --git a/lib/methods/randomchecktask.cpp b/lib/methods/randomchecktask.cpp index 9b133ef0e..ae7547ee5 100644 --- a/lib/methods/randomchecktask.cpp +++ b/lib/methods/randomchecktask.cpp @@ -13,10 +13,10 @@ using namespace icinga; -REGISTER_FUNCTION_NONCONST(Internal, RandomCheck, &RandomCheckTask::ScriptFunc, "checkable:cr:resolvedMacros:useResolvedMacros"); +REGISTER_FUNCTION_NONCONST(Internal, RandomCheck, &RandomCheckTask::ScriptFunc, "checkable:cr:producer:resolvedMacros:useResolvedMacros"); void RandomCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) { REQUIRE_NOT_NULL(checkable); REQUIRE_NOT_NULL(cr); @@ -60,6 +60,6 @@ void RandomCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckRes cr->SetState(state); cr->SetCommand(commandName); - checkable->ProcessCheckResult(cr); + checkable->ProcessCheckResult(cr, producer); } } diff --git a/lib/methods/randomchecktask.hpp b/lib/methods/randomchecktask.hpp index 00ce663dc..82a0c48a7 100644 --- a/lib/methods/randomchecktask.hpp +++ b/lib/methods/randomchecktask.hpp @@ -18,7 +18,7 @@ class RandomCheckTask { public: static void ScriptFunc(const Checkable::Ptr& service, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); private: RandomCheckTask(); diff --git a/lib/methods/sleepchecktask.cpp b/lib/methods/sleepchecktask.cpp index 05ed2675a..975fde697 100644 --- a/lib/methods/sleepchecktask.cpp +++ b/lib/methods/sleepchecktask.cpp @@ -9,10 +9,10 @@ using namespace icinga; -REGISTER_FUNCTION_NONCONST(Internal, SleepCheck, &SleepCheckTask::ScriptFunc, "checkable:cr:resolvedMacros:useResolvedMacros"); +REGISTER_FUNCTION_NONCONST(Internal, SleepCheck, &SleepCheckTask::ScriptFunc, "checkable:cr:producer:resolvedMacros:useResolvedMacros"); void SleepCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros) { REQUIRE_NOT_NULL(checkable); REQUIRE_NOT_NULL(cr); @@ -62,6 +62,6 @@ void SleepCheckTask::ScriptFunc(const Checkable::Ptr& checkable, const CheckResu cr->SetExecutionEnd(now); cr->SetCommand(commandName); - checkable->ProcessCheckResult(cr); + checkable->ProcessCheckResult(cr, producer); } } diff --git a/lib/methods/sleepchecktask.hpp b/lib/methods/sleepchecktask.hpp index ed8d59089..38019a5bc 100644 --- a/lib/methods/sleepchecktask.hpp +++ b/lib/methods/sleepchecktask.hpp @@ -19,7 +19,7 @@ class SleepCheckTask { public: static void ScriptFunc(const Checkable::Ptr& checkable, const CheckResult::Ptr& cr, - const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); + const WaitGroup::Ptr& producer, const Dictionary::Ptr& resolvedMacros, bool useResolvedMacros); private: SleepCheckTask(); diff --git a/lib/remote/apilistener.hpp b/lib/remote/apilistener.hpp index 3aa4f40db..d7965508d 100644 --- a/lib/remote/apilistener.hpp +++ b/lib/remote/apilistener.hpp @@ -152,6 +152,11 @@ public: double GetTlsHandshakeTimeout() const override; void SetTlsHandshakeTimeout(double value, bool suppress_events, const Value& cookie) override; + WaitGroup::Ptr GetWaitGroup() const + { + return m_WaitGroup; + } + protected: void OnConfigLoaded() override; void OnAllConfigLoaded() override; diff --git a/test/icinga-checkable-flapping.cpp b/test/icinga-checkable-flapping.cpp index bc3056425..a63c78fc4 100644 --- a/test/icinga-checkable-flapping.cpp +++ b/test/icinga-checkable-flapping.cpp @@ -73,7 +73,7 @@ BOOST_AUTO_TEST_CASE(host_not_flapping) int i = 0; while (i++ < 10) { // For some reason, elusive to me, the first check is a state change - host->ProcessCheckResult(MakeCheckResult(ServiceOK)); + host->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); LogFlapping(host); LogHostStatus(host); @@ -107,9 +107,9 @@ BOOST_AUTO_TEST_CASE(host_flapping) int i = 0; while (i++ < 25) { if (i % 2) - host->ProcessCheckResult(MakeCheckResult(ServiceOK)); + host->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); else - host->ProcessCheckResult(MakeCheckResult(ServiceWarning)); + host->ProcessCheckResult(MakeCheckResult(ServiceWarning), new StoppableWaitGroup()); LogFlapping(host); LogHostStatus(host); @@ -144,18 +144,18 @@ BOOST_AUTO_TEST_CASE(host_flapping_recover) Utility::SetTime(0); // A few warning - host->ProcessCheckResult(MakeCheckResult(ServiceWarning)); - host->ProcessCheckResult(MakeCheckResult(ServiceWarning)); - host->ProcessCheckResult(MakeCheckResult(ServiceWarning)); - host->ProcessCheckResult(MakeCheckResult(ServiceWarning)); + host->ProcessCheckResult(MakeCheckResult(ServiceWarning), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceWarning), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceWarning), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceWarning), new StoppableWaitGroup()); LogFlapping(host); LogHostStatus(host); for (int i = 0; i <= 7; i++) { if (i % 2) - host->ProcessCheckResult(MakeCheckResult(ServiceOK)); + host->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); else - host->ProcessCheckResult(MakeCheckResult(ServiceWarning)); + host->ProcessCheckResult(MakeCheckResult(ServiceWarning), new StoppableWaitGroup()); } LogFlapping(host); @@ -171,7 +171,7 @@ BOOST_AUTO_TEST_CASE(host_flapping_recover) BOOST_CHECK(host->GetFlappingCurrent() > 25.0); BOOST_CHECK(host->IsFlapping()); - host->ProcessCheckResult(MakeCheckResult(ServiceWarning)); + host->ProcessCheckResult(MakeCheckResult(ServiceWarning), new StoppableWaitGroup()); LogFlapping(host); LogHostStatus(host); count++; @@ -203,40 +203,40 @@ BOOST_AUTO_TEST_CASE(host_flapping_docs_example) Utility::SetTime(0); - host->ProcessCheckResult(MakeCheckResult(ServiceCritical)); - host->ProcessCheckResult(MakeCheckResult(ServiceCritical)); - host->ProcessCheckResult(MakeCheckResult(ServiceCritical)); - host->ProcessCheckResult(MakeCheckResult(ServiceWarning)); - host->ProcessCheckResult(MakeCheckResult(ServiceCritical)); - host->ProcessCheckResult(MakeCheckResult(ServiceWarning)); - host->ProcessCheckResult(MakeCheckResult(ServiceOK)); - host->ProcessCheckResult(MakeCheckResult(ServiceOK)); - host->ProcessCheckResult(MakeCheckResult(ServiceOK)); - host->ProcessCheckResult(MakeCheckResult(ServiceOK)); - host->ProcessCheckResult(MakeCheckResult(ServiceWarning)); - host->ProcessCheckResult(MakeCheckResult(ServiceWarning)); - host->ProcessCheckResult(MakeCheckResult(ServiceWarning)); - host->ProcessCheckResult(MakeCheckResult(ServiceWarning)); - host->ProcessCheckResult(MakeCheckResult(ServiceOK)); - host->ProcessCheckResult(MakeCheckResult(ServiceOK)); - host->ProcessCheckResult(MakeCheckResult(ServiceOK)); - host->ProcessCheckResult(MakeCheckResult(ServiceWarning)); - host->ProcessCheckResult(MakeCheckResult(ServiceWarning)); - host->ProcessCheckResult(MakeCheckResult(ServiceWarning)); - host->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + host->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceWarning), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceWarning), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceWarning), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceWarning), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceWarning), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceWarning), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceWarning), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceWarning), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceWarning), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); LogFlapping(host); LogHostStatus(host); BOOST_CHECK(host->GetFlappingCurrent() == 39.1); BOOST_CHECK(host->IsFlapping()); - host->ProcessCheckResult(MakeCheckResult(ServiceCritical)); - host->ProcessCheckResult(MakeCheckResult(ServiceCritical)); - host->ProcessCheckResult(MakeCheckResult(ServiceCritical)); - host->ProcessCheckResult(MakeCheckResult(ServiceCritical)); - host->ProcessCheckResult(MakeCheckResult(ServiceCritical)); - host->ProcessCheckResult(MakeCheckResult(ServiceCritical)); - host->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + host->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); + host->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); LogFlapping(host); LogHostStatus(host); diff --git a/test/icinga-checkresult.cpp b/test/icinga-checkresult.cpp index fdc78915e..438dee87d 100644 --- a/test/icinga-checkresult.cpp +++ b/test/icinga-checkresult.cpp @@ -69,7 +69,7 @@ BOOST_AUTO_TEST_CASE(host_1attempt) CheckNotification(host, false); std::cout << "First check result (unknown)" << std::endl; - host->ProcessCheckResult(MakeCheckResult(ServiceUnknown)); + host->ProcessCheckResult(MakeCheckResult(ServiceUnknown), new StoppableWaitGroup()); BOOST_CHECK(host->GetState() == HostDown); BOOST_CHECK(host->GetStateType() == StateTypeHard); BOOST_CHECK(host->GetCheckAttempt() == 1); @@ -77,7 +77,7 @@ BOOST_AUTO_TEST_CASE(host_1attempt) CheckNotification(host, true, NotificationProblem); std::cout << "Second check result (ok)" << std::endl; - host->ProcessCheckResult(MakeCheckResult(ServiceOK)); + host->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); BOOST_CHECK(host->GetState() == HostUp); BOOST_CHECK(host->GetStateType() == StateTypeHard); BOOST_CHECK(host->GetCheckAttempt() == 1); @@ -85,7 +85,7 @@ BOOST_AUTO_TEST_CASE(host_1attempt) CheckNotification(host, true, NotificationRecovery); std::cout << "Third check result (critical)" << std::endl; - host->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + host->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); BOOST_CHECK(host->GetState() == HostDown); BOOST_CHECK(host->GetStateType() == StateTypeHard); BOOST_CHECK(host->GetCheckAttempt() == 1); @@ -93,7 +93,7 @@ BOOST_AUTO_TEST_CASE(host_1attempt) CheckNotification(host, true, NotificationProblem); std::cout << "Fourth check result (ok)" << std::endl; - host->ProcessCheckResult(MakeCheckResult(ServiceOK)); + host->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); BOOST_CHECK(host->GetState() == HostUp); BOOST_CHECK(host->GetStateType() == StateTypeHard); BOOST_CHECK(host->GetCheckAttempt() == 1); @@ -126,7 +126,7 @@ BOOST_AUTO_TEST_CASE(host_2attempts) CheckNotification(host, false); std::cout << "First check result (unknown)" << std::endl; - host->ProcessCheckResult(MakeCheckResult(ServiceUnknown)); + host->ProcessCheckResult(MakeCheckResult(ServiceUnknown), new StoppableWaitGroup()); BOOST_CHECK(host->GetState() == HostDown); BOOST_CHECK(host->GetStateType() == StateTypeSoft); BOOST_CHECK(host->GetCheckAttempt() == 1); @@ -134,7 +134,7 @@ BOOST_AUTO_TEST_CASE(host_2attempts) CheckNotification(host, false); std::cout << "Second check result (critical)" << std::endl; - host->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + host->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); BOOST_CHECK(host->GetState() == HostDown); BOOST_CHECK(host->GetStateType() == StateTypeHard); BOOST_CHECK(host->GetCheckAttempt() == 1); @@ -142,7 +142,7 @@ BOOST_AUTO_TEST_CASE(host_2attempts) CheckNotification(host, true, NotificationProblem); std::cout << "Third check result (ok)" << std::endl; - host->ProcessCheckResult(MakeCheckResult(ServiceOK)); + host->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); BOOST_CHECK(host->GetState() == HostUp); BOOST_CHECK(host->GetStateType() == StateTypeHard); BOOST_CHECK(host->GetCheckAttempt() == 1); @@ -150,7 +150,7 @@ BOOST_AUTO_TEST_CASE(host_2attempts) CheckNotification(host, true, NotificationRecovery); std::cout << "Fourth check result (critical)" << std::endl; - host->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + host->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); BOOST_CHECK(host->GetState() == HostDown); BOOST_CHECK(host->GetStateType() == StateTypeSoft); BOOST_CHECK(host->GetCheckAttempt() == 1); @@ -158,7 +158,7 @@ BOOST_AUTO_TEST_CASE(host_2attempts) CheckNotification(host, false); std::cout << "Fifth check result (ok)" << std::endl; - host->ProcessCheckResult(MakeCheckResult(ServiceOK)); + host->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); BOOST_CHECK(host->GetState() == HostUp); BOOST_CHECK(host->GetStateType() == StateTypeHard); BOOST_CHECK(host->GetCheckAttempt() == 1); @@ -191,7 +191,7 @@ BOOST_AUTO_TEST_CASE(host_3attempts) CheckNotification(host, false); std::cout << "First check result (unknown)" << std::endl; - host->ProcessCheckResult(MakeCheckResult(ServiceUnknown)); + host->ProcessCheckResult(MakeCheckResult(ServiceUnknown), new StoppableWaitGroup()); BOOST_CHECK(host->GetState() == HostDown); BOOST_CHECK(host->GetStateType() == StateTypeSoft); BOOST_CHECK(host->GetCheckAttempt() == 1); @@ -199,7 +199,7 @@ BOOST_AUTO_TEST_CASE(host_3attempts) CheckNotification(host, false); std::cout << "Second check result (critical)" << std::endl; - host->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + host->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); BOOST_CHECK(host->GetState() == HostDown); BOOST_CHECK(host->GetStateType() == StateTypeSoft); BOOST_CHECK(host->GetCheckAttempt() == 2); @@ -207,7 +207,7 @@ BOOST_AUTO_TEST_CASE(host_3attempts) CheckNotification(host, false); std::cout << "Third check result (critical)" << std::endl; - host->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + host->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); BOOST_CHECK(host->GetState() == HostDown); BOOST_CHECK(host->GetStateType() == StateTypeHard); BOOST_CHECK(host->GetCheckAttempt() == 1); @@ -215,7 +215,7 @@ BOOST_AUTO_TEST_CASE(host_3attempts) CheckNotification(host, true, NotificationProblem); std::cout << "Fourth check result (ok)" << std::endl; - host->ProcessCheckResult(MakeCheckResult(ServiceOK)); + host->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); BOOST_CHECK(host->GetState() == HostUp); BOOST_CHECK(host->GetStateType() == StateTypeHard); BOOST_CHECK(host->GetCheckAttempt() == 1); @@ -223,7 +223,7 @@ BOOST_AUTO_TEST_CASE(host_3attempts) CheckNotification(host, true, NotificationRecovery); std::cout << "Fifth check result (critical)" << std::endl; - host->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + host->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); BOOST_CHECK(host->GetState() == HostDown); BOOST_CHECK(host->GetStateType() == StateTypeSoft); BOOST_CHECK(host->GetCheckAttempt() == 1); @@ -231,7 +231,7 @@ BOOST_AUTO_TEST_CASE(host_3attempts) CheckNotification(host, false); std::cout << "Sixth check result (ok)" << std::endl; - host->ProcessCheckResult(MakeCheckResult(ServiceOK)); + host->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); BOOST_CHECK(host->GetState() == HostUp); BOOST_CHECK(host->GetStateType() == StateTypeHard); BOOST_CHECK(host->GetCheckAttempt() == 1); @@ -264,7 +264,7 @@ BOOST_AUTO_TEST_CASE(service_1attempt) CheckNotification(service, false); std::cout << "First check result (unknown)" << std::endl; - service->ProcessCheckResult(MakeCheckResult(ServiceUnknown)); + service->ProcessCheckResult(MakeCheckResult(ServiceUnknown), new StoppableWaitGroup()); BOOST_CHECK(service->GetState() == ServiceUnknown); BOOST_CHECK(service->GetStateType() == StateTypeHard); BOOST_CHECK(service->GetCheckAttempt() == 1); @@ -272,7 +272,7 @@ BOOST_AUTO_TEST_CASE(service_1attempt) CheckNotification(service, true, NotificationProblem); std::cout << "Second check result (ok)" << std::endl; - service->ProcessCheckResult(MakeCheckResult(ServiceOK)); + service->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); BOOST_CHECK(service->GetState() == ServiceOK); BOOST_CHECK(service->GetStateType() == StateTypeHard); BOOST_CHECK(service->GetCheckAttempt() == 1); @@ -280,7 +280,7 @@ BOOST_AUTO_TEST_CASE(service_1attempt) CheckNotification(service, true, NotificationRecovery); std::cout << "Third check result (critical)" << std::endl; - service->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + service->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); BOOST_CHECK(service->GetState() == ServiceCritical); BOOST_CHECK(service->GetStateType() == StateTypeHard); BOOST_CHECK(service->GetCheckAttempt() == 1); @@ -288,7 +288,7 @@ BOOST_AUTO_TEST_CASE(service_1attempt) CheckNotification(service, true, NotificationProblem); std::cout << "Fourth check result (ok)" << std::endl; - service->ProcessCheckResult(MakeCheckResult(ServiceOK)); + service->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); BOOST_CHECK(service->GetState() == ServiceOK); BOOST_CHECK(service->GetStateType() == StateTypeHard); BOOST_CHECK(service->GetCheckAttempt() == 1); @@ -321,7 +321,7 @@ BOOST_AUTO_TEST_CASE(service_2attempts) CheckNotification(service, false); std::cout << "First check result (unknown)" << std::endl; - service->ProcessCheckResult(MakeCheckResult(ServiceUnknown)); + service->ProcessCheckResult(MakeCheckResult(ServiceUnknown), new StoppableWaitGroup()); BOOST_CHECK(service->GetState() == ServiceUnknown); BOOST_CHECK(service->GetStateType() == StateTypeSoft); BOOST_CHECK(service->GetCheckAttempt() == 1); @@ -329,7 +329,7 @@ BOOST_AUTO_TEST_CASE(service_2attempts) CheckNotification(service, false); std::cout << "Second check result (critical)" << std::endl; - service->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + service->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); BOOST_CHECK(service->GetState() == ServiceCritical); BOOST_CHECK(service->GetStateType() == StateTypeHard); BOOST_CHECK(service->GetCheckAttempt() == 1); @@ -337,7 +337,7 @@ BOOST_AUTO_TEST_CASE(service_2attempts) CheckNotification(service, true, NotificationProblem); std::cout << "Third check result (ok)" << std::endl; - service->ProcessCheckResult(MakeCheckResult(ServiceOK)); + service->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); BOOST_CHECK(service->GetState() == ServiceOK); BOOST_CHECK(service->GetStateType() == StateTypeHard); BOOST_CHECK(service->GetCheckAttempt() == 1); @@ -345,7 +345,7 @@ BOOST_AUTO_TEST_CASE(service_2attempts) CheckNotification(service, true, NotificationRecovery); std::cout << "Fourth check result (critical)" << std::endl; - service->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + service->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); BOOST_CHECK(service->GetState() == ServiceCritical); BOOST_CHECK(service->GetStateType() == StateTypeSoft); BOOST_CHECK(service->GetCheckAttempt() == 1); @@ -353,7 +353,7 @@ BOOST_AUTO_TEST_CASE(service_2attempts) CheckNotification(service, false); std::cout << "Fifth check result (ok)" << std::endl; - service->ProcessCheckResult(MakeCheckResult(ServiceOK)); + service->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); BOOST_CHECK(service->GetState() == ServiceOK); BOOST_CHECK(service->GetStateType() == StateTypeHard); BOOST_CHECK(service->GetCheckAttempt() == 1); @@ -386,7 +386,7 @@ BOOST_AUTO_TEST_CASE(service_3attempts) CheckNotification(service, false); std::cout << "First check result (unknown)" << std::endl; - service->ProcessCheckResult(MakeCheckResult(ServiceUnknown)); + service->ProcessCheckResult(MakeCheckResult(ServiceUnknown), new StoppableWaitGroup()); BOOST_CHECK(service->GetState() == ServiceUnknown); BOOST_CHECK(service->GetStateType() == StateTypeSoft); BOOST_CHECK(service->GetCheckAttempt() == 1); @@ -394,7 +394,7 @@ BOOST_AUTO_TEST_CASE(service_3attempts) CheckNotification(service, false); std::cout << "Second check result (critical)" << std::endl; - service->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + service->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); BOOST_CHECK(service->GetState() == ServiceCritical); BOOST_CHECK(service->GetStateType() == StateTypeSoft); BOOST_CHECK(service->GetCheckAttempt() == 2); @@ -402,7 +402,7 @@ BOOST_AUTO_TEST_CASE(service_3attempts) CheckNotification(service, false); std::cout << "Third check result (critical)" << std::endl; - service->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + service->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); BOOST_CHECK(service->GetState() == ServiceCritical); BOOST_CHECK(service->GetStateType() == StateTypeHard); BOOST_CHECK(service->GetCheckAttempt() == 1); @@ -410,7 +410,7 @@ BOOST_AUTO_TEST_CASE(service_3attempts) CheckNotification(service, true, NotificationProblem); std::cout << "Fourth check result (ok)" << std::endl; - service->ProcessCheckResult(MakeCheckResult(ServiceOK)); + service->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); BOOST_CHECK(service->GetState() == ServiceOK); BOOST_CHECK(service->GetStateType() == StateTypeHard); BOOST_CHECK(service->GetCheckAttempt() == 1); @@ -418,7 +418,7 @@ BOOST_AUTO_TEST_CASE(service_3attempts) CheckNotification(service, true, NotificationRecovery); std::cout << "Fifth check result (critical)" << std::endl; - service->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + service->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); BOOST_CHECK(service->GetState() == ServiceCritical); BOOST_CHECK(service->GetStateType() == StateTypeSoft); BOOST_CHECK(service->GetCheckAttempt() == 1); @@ -426,7 +426,7 @@ BOOST_AUTO_TEST_CASE(service_3attempts) CheckNotification(service, false); std::cout << "Sixth check result (ok)" << std::endl; - service->ProcessCheckResult(MakeCheckResult(ServiceOK)); + service->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); BOOST_CHECK(service->GetState() == ServiceOK); BOOST_CHECK(service->GetStateType() == StateTypeHard); BOOST_CHECK(service->GetCheckAttempt() == 1); @@ -470,7 +470,7 @@ BOOST_AUTO_TEST_CASE(host_flapping_notification) for (int i = 0; i < 10; i++) { ServiceState state = (i % 2 == 0 ? ServiceOK : ServiceCritical); - host->ProcessCheckResult(MakeCheckResult(state)); + host->ProcessCheckResult(MakeCheckResult(state), new StoppableWaitGroup()); Utility::IncrementTime(timeStepInterval); } @@ -481,7 +481,7 @@ BOOST_AUTO_TEST_CASE(host_flapping_notification) std::cout << "Now calm down..." << std::endl; for (int i = 0; i < 20; i++) { - host->ProcessCheckResult(MakeCheckResult(ServiceOK)); + host->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); Utility::IncrementTime(timeStepInterval); } @@ -527,7 +527,7 @@ BOOST_AUTO_TEST_CASE(service_flapping_notification) for (int i = 0; i < 10; i++) { ServiceState state = (i % 2 == 0 ? ServiceOK : ServiceCritical); - service->ProcessCheckResult(MakeCheckResult(state)); + service->ProcessCheckResult(MakeCheckResult(state), new StoppableWaitGroup()); Utility::IncrementTime(timeStepInterval); } @@ -540,7 +540,7 @@ BOOST_AUTO_TEST_CASE(service_flapping_notification) std::cout << "Now calm down..." << std::endl; for (int i = 0; i < 20; i++) { - service->ProcessCheckResult(MakeCheckResult(ServiceOK)); + service->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); Utility::IncrementTime(timeStepInterval); } @@ -585,7 +585,7 @@ BOOST_AUTO_TEST_CASE(service_flapping_problem_notifications) for (int i = 0; i < 10; i++) { ServiceState state = (i % 2 == 0 ? ServiceOK : ServiceCritical); - service->ProcessCheckResult(MakeCheckResult(state)); + service->ProcessCheckResult(MakeCheckResult(state), new StoppableWaitGroup()); Utility::IncrementTime(timeStepInterval); } @@ -595,11 +595,11 @@ BOOST_AUTO_TEST_CASE(service_flapping_problem_notifications) //Insert enough check results to get into hard problem state but staying flapping - service->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + service->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); Utility::IncrementTime(timeStepInterval); - service->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + service->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); Utility::IncrementTime(timeStepInterval); - service->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + service->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); Utility::IncrementTime(timeStepInterval); @@ -611,7 +611,7 @@ BOOST_AUTO_TEST_CASE(service_flapping_problem_notifications) // Calm down while (service->IsFlapping()) { - service->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + service->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); Utility::IncrementTime(timeStepInterval); } @@ -641,7 +641,7 @@ BOOST_AUTO_TEST_CASE(service_flapping_problem_notifications) // Known failure, see #5713 // CheckNotification(service, true, NotificationProblem); - service->ProcessCheckResult(MakeCheckResult(ServiceOK)); + service->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); Utility::IncrementTime(timeStepInterval); // Known failure, see #5713 @@ -686,7 +686,7 @@ BOOST_AUTO_TEST_CASE(service_flapping_ok_into_bad) for (int i = 0; i < 10; i++) { ServiceState state = (i % 2 == 0 ? ServiceOK : ServiceCritical); - service->ProcessCheckResult(MakeCheckResult(state)); + service->ProcessCheckResult(MakeCheckResult(state), new StoppableWaitGroup()); Utility::IncrementTime(timeStepInterval); } @@ -696,11 +696,11 @@ BOOST_AUTO_TEST_CASE(service_flapping_ok_into_bad) //Insert enough check results to get into hard problem state but staying flapping - service->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + service->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); Utility::IncrementTime(timeStepInterval); - service->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + service->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); Utility::IncrementTime(timeStepInterval); - service->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + service->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); Utility::IncrementTime(timeStepInterval); @@ -712,13 +712,13 @@ BOOST_AUTO_TEST_CASE(service_flapping_ok_into_bad) // Calm down while (service->IsFlapping()) { - service->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + service->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); Utility::IncrementTime(timeStepInterval); } CheckNotification(service, true, NotificationFlappingEnd); - service->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + service->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); Utility::IncrementTime(timeStepInterval); BOOST_CHECK(service->IsFlapping() == false); @@ -767,7 +767,7 @@ BOOST_AUTO_TEST_CASE(service_flapping_ok_over_bad_into_ok) for (int i = 0; i < 10; i++) { ServiceState state = (i % 2 == 0 ? ServiceOK : ServiceCritical); - service->ProcessCheckResult(MakeCheckResult(state)); + service->ProcessCheckResult(MakeCheckResult(state), new StoppableWaitGroup()); Utility::IncrementTime(timeStepInterval); } @@ -777,11 +777,11 @@ BOOST_AUTO_TEST_CASE(service_flapping_ok_over_bad_into_ok) //Insert enough check results to get into hard problem state but staying flapping - service->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + service->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); Utility::IncrementTime(timeStepInterval); - service->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + service->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); Utility::IncrementTime(timeStepInterval); - service->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + service->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); Utility::IncrementTime(timeStepInterval); @@ -793,13 +793,13 @@ BOOST_AUTO_TEST_CASE(service_flapping_ok_over_bad_into_ok) // Calm down while (service->IsFlapping()) { - service->ProcessCheckResult(MakeCheckResult(ServiceCritical)); + service->ProcessCheckResult(MakeCheckResult(ServiceCritical), new StoppableWaitGroup()); Utility::IncrementTime(timeStepInterval); } CheckNotification(service, true, NotificationFlappingEnd); - service->ProcessCheckResult(MakeCheckResult(ServiceOK)); + service->ProcessCheckResult(MakeCheckResult(ServiceOK), new StoppableWaitGroup()); Utility::IncrementTime(timeStepInterval); BOOST_CHECK(service->IsFlapping() == false); @@ -894,7 +894,7 @@ BOOST_AUTO_TEST_CASE(suppressed_notification) for (int i = 0; i < checkAttempts; i++) { std::cout << " ProcessCheckResult(" << Service::StateToString(initialState) << ")" << std::endl; - service->ProcessCheckResult(MakeCheckResult(initialState)); + service->ProcessCheckResult(MakeCheckResult(initialState), new StoppableWaitGroup()); } BOOST_CHECK(service->GetState() == initialState); @@ -972,7 +972,7 @@ BOOST_AUTO_TEST_CASE(suppressed_notification) // Process check results for the state sequence. for (ServiceState s : sequence) { std::cout << " ProcessCheckResult(" << Service::StateToString(s) << ")" << std::endl; - service->ProcessCheckResult(MakeCheckResult(s)); + service->ProcessCheckResult(MakeCheckResult(s), new StoppableWaitGroup()); BOOST_CHECK(service->GetState() == s); if (checkAttempts == 1) { BOOST_CHECK(service->GetStateType() == StateTypeHard); @@ -1002,7 +1002,7 @@ BOOST_AUTO_TEST_CASE(suppressed_notification) for (int i = 0; i < checkAttempts && service->GetStateType() == StateTypeSoft; i++) { std::cout << " ProcessCheckResult(" << Service::StateToString(sequence.back()) << ")" << std::endl; - service->ProcessCheckResult(MakeCheckResult(sequence.back())); + service->ProcessCheckResult(MakeCheckResult(sequence.back()), new StoppableWaitGroup()); BOOST_CHECK(service->GetState() == sequence.back()); } } diff --git a/test/livestatus.cpp b/test/livestatus.cpp index 6aafa3be3..a989eedca 100644 --- a/test/livestatus.cpp +++ b/test/livestatus.cpp @@ -15,7 +15,7 @@ String LivestatusQueryHelper(const std::vector& lines) std::stringstream stream; StdioStream::Ptr sstream = new StdioStream(&stream, false); - query->Execute(sstream); + query->Execute(new StoppableWaitGroup(), sstream); String output; String result; From 36743f3100ad5f76dacb6077878ef6b9cfa20999 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 20 May 2025 13:02:26 +0200 Subject: [PATCH 323/415] Checkable#ProcessCheckResult(): discard CR or delay its producers shutdown --- lib/icinga/checkable-check.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/icinga/checkable-check.cpp b/lib/icinga/checkable-check.cpp index b2b5ca45e..2d34ec644 100644 --- a/lib/icinga/checkable-check.cpp +++ b/lib/icinga/checkable-check.cpp @@ -14,6 +14,7 @@ #include "base/convert.hpp" #include "base/utility.hpp" #include "base/context.hpp" +#include using namespace icinga; @@ -101,6 +102,7 @@ Checkable::ProcessingResult Checkable::ProcessCheckResult(const CheckResult::Ptr using Result = Checkable::ProcessingResult; VERIFY(cr); + VERIFY(producer); { ObjectLock olock(this); @@ -135,6 +137,14 @@ Checkable::ProcessingResult Checkable::ProcessCheckResult(const CheckResult::Ptr cr->SetCheckSource(command_endpoint->GetName()); } + std::shared_lock producerLock (*producer, std::try_to_lock); + + if (!producerLock) { + // Discard the check result to not delay the current reload. + // We'll re-run the check immediately after the reload. + return Result::CheckableInactive; + } + /* agent checks go through the api */ if (command_endpoint && GetExtension("agent_check")) { ApiListener::Ptr listener = ApiListener::GetInstance(); From 4f351f625f2960d36b41a9f8a23c7bea0b98c115 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 23 May 2025 15:47:02 +0200 Subject: [PATCH 324/415] SharedObject: delete unused methods None of the derived classes use them, none shall have to explicitly delete them. --- lib/base/shared-object.hpp | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/lib/base/shared-object.hpp b/lib/base/shared-object.hpp index 58636dccb..396969db6 100644 --- a/lib/base/shared-object.hpp +++ b/lib/base/shared-object.hpp @@ -31,23 +31,8 @@ protected: { } - inline SharedObject(const SharedObject&) : SharedObject() - { - } - - inline SharedObject(SharedObject&&) : SharedObject() - { - } - - inline SharedObject& operator=(const SharedObject&) - { - return *this; - } - - inline SharedObject& operator=(SharedObject&&) - { - return *this; - } + SharedObject(const SharedObject&) = delete; + SharedObject& operator=(const SharedObject&) = delete; inline virtual ~SharedObject() = default; From b1e08ba7a96a917100f5f170895260712a721fe8 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 23 May 2025 17:24:05 +0200 Subject: [PATCH 325/415] docs: document the new object distribution behavior --- doc/19-technical-concepts.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/doc/19-technical-concepts.md b/doc/19-technical-concepts.md index de68bdd63..46819f7d6 100644 --- a/doc/19-technical-concepts.md +++ b/doc/19-technical-concepts.md @@ -632,15 +632,12 @@ The algorithm works like this: * Set the authority (true or false) The object authority calculation works "offline" without any message exchange. -Each instance alculates the SDBM hash of the config object name, puts that in contrast -modulo the connected endpoints size. -This index is used to lookup the corresponding endpoint in the connected endpoints array, -including the local endpoint. Whether the local endpoint is equal to the selected endpoint, -or not, this sets the authority to `true` or `false`. - -```cpp -authority = endpoints[Utility::SDBM(object->GetName()) % endpoints.size()] == my_endpoint; -``` +Each instance calculates the SDBM hash of the config object name. However, for objects bound to some +host, i.e. the object name is composed of `!`, the SDBM hash is calculated based +on the host name only instead of the full object name. That way, each child object like services, downtimes, +etc. will be assigned to the same endpoint as the host object itself. The resulting hash modulo (`%`) the number of +connected endpoints produces the index of the endpoint which is authoritative for this config object. If the +endpoint at this index is equal to the local endpoint, the authority is set to `true`, otherwise it is set to `false`. `ConfigObject::SetAuthority(bool authority)` triggers the following events: From ab58ff2928e9745bfcf63e6cc99f84b3aa5b1d23 Mon Sep 17 00:00:00 2001 From: Yannick Martin Date: Mon, 26 May 2025 15:05:42 +0200 Subject: [PATCH 326/415] fix api deadlock that can appears on two simultaneous actions With this mutex, we can have deadlock in the following case: 1/ Thread A processes a /v1/actions/acknowledge-problem request and locks the checkable 2/ Thread B processes a /v1/actions/add-comment and enters first the ConfigItem::ActivateItems() method and locks the static mutex there and starts the just created comment object, which triggers the OnCommentAdded() event. 3/ Thread A wants to activate the just created ack comment as well but since the mutex is already locked by TB, it blocks. 4/ Thread B's OnCommentAdded() even dispatch causes the IcingaDB::CommentAddedHandler() to be called and implicitly triggers full state update for the checkable. Now, the state serialization of that checkable (remember that's the same checkable currently locked by TA) also includes computing its severity, thus it calls either service->GetSeverity() or host->GetSeverity(). However, since computing the checkable severity (as of now) requires acquiring the object lock, and boom - they deadlock each other. --- lib/config/configitem.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/config/configitem.cpp b/lib/config/configitem.cpp index 9dc0f1aa2..6401b2d70 100644 --- a/lib/config/configitem.cpp +++ b/lib/config/configitem.cpp @@ -679,9 +679,6 @@ bool ConfigItem::CommitItems(const ActivationContext::Ptr& context, WorkQueue& u bool ConfigItem::ActivateItems(const std::vector& newItems, bool runtimeCreated, bool mainConfigActivation, bool withModAttrs, const Value& cookie) { - static std::mutex mtx; - std::unique_lock lock(mtx); - if (withModAttrs) { /* restore modified attributes */ if (Utility::PathExists(Configuration::ModAttrPath)) { From 7d2f1c2030a17ae3dc464c3e8dabfdef0fb78cef Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 27 May 2025 16:53:13 +0200 Subject: [PATCH 327/415] Drop Windows VISTA from the supported platform Boost `1.88.0` introduced a feature [^1] that makes use of the Windows API, but it uses API functions that are only available with `PSAPI_VERSION >= 2` and Windows VISTA only supports `PSAPI_VERSION == 1`. Actually, that new feature can also be disabled by setting the `BOOST_STACKTRACE_DISABLE_OFFSET_ADDR_BASE` macro, but since it seems to be a useful feature and isn't even disabled by default, we can just drop it that ancient Windows version instead of disabling it. [^1]: https://github.com/boostorg/stacktrace/pull/200 --- lib/base/win32.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/base/win32.hpp b/lib/base/win32.hpp index 064c5d669..80d8729f3 100644 --- a/lib/base/win32.hpp +++ b/lib/base/win32.hpp @@ -5,7 +5,7 @@ #define WIN32_LEAN_AND_MEAN #ifndef _WIN32_WINNT -#define _WIN32_WINNT _WIN32_WINNT_VISTA +#define _WIN32_WINNT _WIN32_WINNT_WIN7 #endif /* _WIN32_WINNT */ #define NOMINMAX #include From c4ddd4886bfad6408f73f80004fb6ddb89611f02 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 27 May 2025 17:20:20 +0200 Subject: [PATCH 328/415] Bump Boost shipped for Windows to v1.88 --- doc/win-dev.ps1 | 2 +- tools/win32/configure-dev.ps1 | 4 ++-- tools/win32/configure.ps1 | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/win-dev.ps1 b/doc/win-dev.ps1 index 5729343e3..46fc1213a 100644 --- a/doc/win-dev.ps1 +++ b/doc/win-dev.ps1 @@ -13,7 +13,7 @@ function ThrowOnNativeFailure { $VsVersion = 2019 $MsvcVersion = '14.2' -$BoostVersion = @(1, 87, 0) +$BoostVersion = @(1, 88, 0) $OpensslVersion = '3_0_16' switch ($Env:BITS) { diff --git a/tools/win32/configure-dev.ps1 b/tools/win32/configure-dev.ps1 index 7729dc58f..a82560d25 100644 --- a/tools/win32/configure-dev.ps1 +++ b/tools/win32/configure-dev.ps1 @@ -34,10 +34,10 @@ if (-not (Test-Path env:OPENSSL_ROOT_DIR)) { $env:OPENSSL_ROOT_DIR = 'c:\local\OpenSSL-Win64' } if (-not (Test-Path env:BOOST_ROOT)) { - $env:BOOST_ROOT = 'c:\local\boost_1_87_0' + $env:BOOST_ROOT = 'c:\local\boost_1_88_0' } if (-not (Test-Path env:BOOST_LIBRARYDIR)) { - $env:BOOST_LIBRARYDIR = 'c:\local\boost_1_87_0\lib64-msvc-14.2' + $env:BOOST_LIBRARYDIR = 'c:\local\boost_1_88_0\lib64-msvc-14.2' } if (-not (Test-Path env:FLEX_BINARY)) { $env:FLEX_BINARY = 'C:\ProgramData\chocolatey\bin\win_flex.exe' diff --git a/tools/win32/configure.ps1 b/tools/win32/configure.ps1 index 008a06de0..db961a2e5 100644 --- a/tools/win32/configure.ps1 +++ b/tools/win32/configure.ps1 @@ -36,10 +36,10 @@ if (-not (Test-Path env:OPENSSL_ROOT_DIR)) { $env:OPENSSL_ROOT_DIR = "c:\local\OpenSSL_3_0_16-Win${env:BITS}" } if (-not (Test-Path env:BOOST_ROOT)) { - $env:BOOST_ROOT = "c:\local\boost_1_87_0-Win${env:BITS}" + $env:BOOST_ROOT = "c:\local\boost_1_88_0-Win${env:BITS}" } if (-not (Test-Path env:BOOST_LIBRARYDIR)) { - $env:BOOST_LIBRARYDIR = "c:\local\boost_1_87_0-Win${env:BITS}\lib${env:BITS}-msvc-14.2" + $env:BOOST_LIBRARYDIR = "c:\local\boost_1_88_0-Win${env:BITS}\lib${env:BITS}-msvc-14.2" } if (-not (Test-Path env:FLEX_BINARY)) { $env:FLEX_BINARY = 'C:\ProgramData\chocolatey\bin\win_flex.exe' From abf2fb392b1f9319323dab4241e3f83fd519b09c Mon Sep 17 00:00:00 2001 From: William Calliari <42240136+w1ll-i-code@users.noreply.github.com> Date: Mon, 10 Mar 2025 13:51:25 +0100 Subject: [PATCH 329/415] Keep object locked until events are dispatched. --- lib/icinga/checkable-check.cpp | 96 +++++++++++++++------------------- 1 file changed, 42 insertions(+), 54 deletions(-) diff --git a/lib/icinga/checkable-check.cpp b/lib/icinga/checkable-check.cpp index 38dca27ab..4fc19d232 100644 --- a/lib/icinga/checkable-check.cpp +++ b/lib/icinga/checkable-check.cpp @@ -104,10 +104,8 @@ Checkable::ProcessingResult Checkable::ProcessCheckResult(const CheckResult::Ptr VERIFY(cr); VERIFY(producer); - { - ObjectLock olock(this); - m_CheckRunning = false; - } + ObjectLock olock(this); + m_CheckRunning = false; double now = Utility::GetTime(); @@ -169,8 +167,6 @@ Checkable::ProcessingResult Checkable::ProcessCheckResult(const CheckResult::Ptr // This will be used to determine whether the on reachability changed event should be triggered. bool affectsPreviousStateChildren(reachable && AffectsChildren()); - ObjectLock olock(this); - CheckResult::Ptr old_cr = GetLastCheckResult(); ServiceState old_state = GetStateRaw(); StateType old_stateType = GetStateType(); @@ -333,8 +329,6 @@ Checkable::ProcessingResult Checkable::ProcessCheckResult(const CheckResult::Ptr if (is_volatile && IsStateOK(old_state) && IsStateOK(new_state)) send_notification = false; /* Don't send notifications for volatile OK -> OK changes. */ - olock.Unlock(); - if (remove_acknowledgement_comments) RemoveAckComments(String(), cr->GetExecutionEnd()); @@ -350,25 +344,14 @@ Checkable::ProcessingResult Checkable::ProcessCheckResult(const CheckResult::Ptr cr->SetVarsAfter(vars_after); - olock.Lock(); - + bool problem_change = false; if (service) { SetLastCheckResult(cr); } else { bool wasProblem = GetProblem(); - SetLastCheckResult(cr); - - if (GetProblem() != wasProblem) { - auto services = host->GetServices(); - olock.Unlock(); - for (auto& service : services) { - Service::OnHostProblemChanged(service, cr, origin); - } - olock.Lock(); - } + problem_change = GetProblem() != wasProblem; } - bool was_flapping = IsFlapping(); UpdateFlappingStatus(cr->GetState()); @@ -399,8 +382,6 @@ Checkable::ProcessingResult Checkable::ProcessCheckResult(const CheckResult::Ptr } } - olock.Unlock(); - #ifdef I2_DEBUG /* I2_DEBUG */ Log(LogDebug, "Checkable") << "Flapping: Checkable " << GetName() @@ -411,36 +392,6 @@ Checkable::ProcessingResult Checkable::ProcessCheckResult(const CheckResult::Ptr << "% current: " << GetFlappingCurrent() << "%."; #endif /* I2_DEBUG */ - if (recovery) { - for (auto& child : children) { - if (child->GetProblem() && child->GetEnableActiveChecks()) { - auto nextCheck (now + Utility::Random() % 60); - - ObjectLock oLock (child); - - if (nextCheck < child->GetNextCheck()) { - child->SetNextCheck(nextCheck); - } - } - } - } - - if (stateChange) { - /* reschedule direct parents */ - for (const Checkable::Ptr& parent : GetParents()) { - if (parent.get() == this) - continue; - - if (!parent->GetEnableActiveChecks()) - continue; - - if (parent->GetNextCheck() >= now + parent->GetRetryInterval()) { - ObjectLock olock(parent); - parent->SetNextCheck(now); - } - } - } - OnNewCheckResult(this, cr, origin); /* signal status updates to for example db_ido */ @@ -521,7 +472,6 @@ Checkable::ProcessingResult Checkable::ProcessCheckResult(const CheckResult::Ptr * stash them into a notification types bitmask for maybe re-sending later. */ - ObjectLock olock (this); int suppressed_types_before (GetSuppressedNotifications()); int suppressed_types_after (suppressed_types_before | suppressed_types); @@ -551,6 +501,44 @@ Checkable::ProcessingResult Checkable::ProcessCheckResult(const CheckResult::Ptr if ((stateChange || hardChange) && !children.empty() && (affectsPreviousStateChildren || AffectsChildren())) OnReachabilityChanged(this, cr, children, origin); + olock->Unlock(); + if (!service && problem_change) { + auto services = host->GetServices(); + for (auto& service : services) { + Service::OnHostProblemChanged(service, cr, origin); + } + } + + if (recovery) { + for (auto& child : children) { + if (child->GetProblem() && child->GetEnableActiveChecks()) { + auto nextCheck (now + Utility::Random() % 60); + + ObjectLock oLock (child); + + if (nextCheck < child->GetNextCheck()) { + child->SetNextCheck(nextCheck); + } + } + } + } + + if (stateChange) { + /* reschedule direct parents */ + for (const Checkable::Ptr& parent : GetParents()) { + if (parent.get() == this) + continue; + + if (!parent->GetEnableActiveChecks()) + continue; + + if (parent->GetNextCheck() >= now + parent->GetRetryInterval()) { + ObjectLock olock(parent); + parent->SetNextCheck(now); + } + } + } + return Result::Ok; } From 9d40de78eb426be2e15969cb256c8ab278c2e091 Mon Sep 17 00:00:00 2001 From: William Calliari <42240136+w1ll-i-code@users.noreply.github.com> Date: Wed, 16 Apr 2025 12:04:53 +0200 Subject: [PATCH 330/415] Address comments from review --- lib/icinga/checkable-check.cpp | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/icinga/checkable-check.cpp b/lib/icinga/checkable-check.cpp index 4fc19d232..fe7b651be 100644 --- a/lib/icinga/checkable-check.cpp +++ b/lib/icinga/checkable-check.cpp @@ -344,14 +344,21 @@ Checkable::ProcessingResult Checkable::ProcessCheckResult(const CheckResult::Ptr cr->SetVarsAfter(vars_after); - bool problem_change = false; if (service) { SetLastCheckResult(cr); } else { bool wasProblem = GetProblem(); + SetLastCheckResult(cr); - problem_change = GetProblem() != wasProblem; + + if (GetProblem() != wasProblem) { + auto services = host->GetServices(); + for (auto& service : services) { + Service::OnHostProblemChanged(service, cr, origin); + } + } } + bool was_flapping = IsFlapping(); UpdateFlappingStatus(cr->GetState()); @@ -501,13 +508,7 @@ Checkable::ProcessingResult Checkable::ProcessCheckResult(const CheckResult::Ptr if ((stateChange || hardChange) && !children.empty() && (affectsPreviousStateChildren || AffectsChildren())) OnReachabilityChanged(this, cr, children, origin); - olock->Unlock(); - if (!service && problem_change) { - auto services = host->GetServices(); - for (auto& service : services) { - Service::OnHostProblemChanged(service, cr, origin); - } - } + olock.Unlock(); if (recovery) { for (auto& child : children) { From a2c84398b77d10579d5f6837df4971129c846dea Mon Sep 17 00:00:00 2001 From: William Calliari <42240136+w1ll-i-code@users.noreply.github.com> Date: Tue, 22 Apr 2025 09:56:49 +0200 Subject: [PATCH 331/415] Add name to AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 0718440ad..688a87eb8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -45,6 +45,7 @@ Brian De Wolf Brian Dockter Bruno Lingner C C Magnus Gustavsson +Calliari William <42240136+w1ll-i-code@users.noreply.github.com> Carlos Cesario Carsten Köbke Chris Boot From 85ddb87c4106cd8adf5447d4a7034b4898e6bacb Mon Sep 17 00:00:00 2001 From: William Calliari <42240136+w1ll-i-code@users.noreply.github.com> Date: Thu, 8 May 2025 10:09:25 +0200 Subject: [PATCH 332/415] Fix Author --- AUTHORS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 688a87eb8..6b648735c 100644 --- a/AUTHORS +++ b/AUTHORS @@ -45,7 +45,6 @@ Brian De Wolf Brian Dockter Bruno Lingner C C Magnus Gustavsson -Calliari William <42240136+w1ll-i-code@users.noreply.github.com> Carlos Cesario Carsten Köbke Chris Boot @@ -302,6 +301,7 @@ vigiroux Vytenis Darulis Wenger Florian Will Frey +William Calliari <42240136+w1ll-i-code@users.noreply.github.com> Winfried Angele Wolfgang Nieder XnS From dd58fd0cde03890c348c138aac43081fd084ad76 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 3 Jun 2025 11:18:05 +0200 Subject: [PATCH 333/415] Drop Ubuntu 20.04 Is EOL by the end of May (31). --- .github/workflows/linux.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 74f7d724d..cbaccc6e6 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -48,7 +48,6 @@ jobs: - registry.suse.com/suse/sle15:15.5 - registry.suse.com/suse/sle15:15.6 - - ubuntu:20.04 - ubuntu:22.04 - ubuntu:24.04 - ubuntu:24.10 From e6fc1b91a7e083c98564dec00bfe217265486073 Mon Sep 17 00:00:00 2001 From: Yannick Martin Date: Tue, 3 Jun 2025 16:33:12 +0200 Subject: [PATCH 334/415] AddComment: return Comment::Ptr instead of String containing the name Mimic 88e5744d54f79ab6a84260bd4a8d2cc0ffd43465 and address nullptr on concurrent add-comment / remove-comment actions. --- lib/icinga/apiactions.cpp | 4 ++-- lib/icinga/comment.cpp | 4 ++-- lib/icinga/comment.hpp | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/icinga/apiactions.cpp b/lib/icinga/apiactions.cpp index 885834edc..781705b64 100644 --- a/lib/icinga/apiactions.cpp +++ b/lib/icinga/apiactions.cpp @@ -316,11 +316,11 @@ Dictionary::Ptr ApiActions::AddComment(const ConfigObject::Ptr& object, return ApiActions::CreateResult(503, "Icinga is reloading."); } - String commentName = Comment::AddComment(checkable, CommentUser, + Comment::Ptr comment = Comment::AddComment(checkable, CommentUser, HttpUtility::GetLastParameter(params, "author"), HttpUtility::GetLastParameter(params, "comment"), false, timestamp); - Comment::Ptr comment = Comment::GetByName(commentName); + String commentName = comment->GetName(); Dictionary::Ptr additional = new Dictionary({ { "name", commentName }, diff --git a/lib/icinga/comment.cpp b/lib/icinga/comment.cpp index 9c0b92359..0d1967440 100644 --- a/lib/icinga/comment.cpp +++ b/lib/icinga/comment.cpp @@ -130,7 +130,7 @@ int Comment::GetNextCommentID() return l_NextCommentID; } -String Comment::AddComment(const Checkable::Ptr& checkable, CommentType entryType, const String& author, +Comment::Ptr Comment::AddComment(const Checkable::Ptr& checkable, CommentType entryType, const String& author, const String& text, bool persistent, double expireTime, bool sticky, const String& id, const MessageOrigin::Ptr& origin) { String fullName; @@ -184,7 +184,7 @@ String Comment::AddComment(const Checkable::Ptr& checkable, CommentType entryTyp Log(LogNotice, "Comment") << "Added comment '" << comment->GetName() << "'."; - return fullName; + return comment; } void Comment::RemoveComment(const String& id, bool removedManually, const String& removedBy, diff --git a/lib/icinga/comment.hpp b/lib/icinga/comment.hpp index 653208478..a230040f8 100644 --- a/lib/icinga/comment.hpp +++ b/lib/icinga/comment.hpp @@ -34,7 +34,7 @@ public: static int GetNextCommentID(); - static String AddComment(const intrusive_ptr& checkable, CommentType entryType, + static Ptr AddComment(const intrusive_ptr& checkable, CommentType entryType, const String& author, const String& text, bool persistent, double expireTime, bool sticky = false, const String& id = String(), const MessageOrigin::Ptr& origin = nullptr); From 9577af8e6de1a40b580c058a6493683b3fa8bb4e Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 26 May 2025 13:57:09 +0200 Subject: [PATCH 335/415] Drop `Checkable#process_check_result()` DSL function Not sure why it's introduced in the first place, maybe for debugging purposes at the early stage of Icinga 2 dev but I failed to see an actual useful use case for it that's worth its maintenance burden. So, this commit dropped it entirely from the DSL language. --- lib/icinga/CMakeLists.txt | 2 +- lib/icinga/checkable-script.cpp | 33 --------------------------------- lib/icinga/checkable.cpp | 2 +- lib/icinga/checkable.hpp | 2 -- 4 files changed, 2 insertions(+), 37 deletions(-) delete mode 100644 lib/icinga/checkable-script.cpp diff --git a/lib/icinga/CMakeLists.txt b/lib/icinga/CMakeLists.txt index 8187d48e8..2286fec94 100644 --- a/lib/icinga/CMakeLists.txt +++ b/lib/icinga/CMakeLists.txt @@ -30,7 +30,7 @@ set(icinga_SOURCES checkable.cpp checkable.hpp checkable-ti.hpp checkable-check.cpp checkable-comment.cpp checkable-dependency.cpp checkable-downtime.cpp checkable-event.cpp checkable-flapping.cpp - checkable-notification.cpp checkable-script.cpp + checkable-notification.cpp checkcommand.cpp checkcommand.hpp checkcommand-ti.hpp checkresult.cpp checkresult.hpp checkresult-ti.hpp cib.cpp cib.hpp diff --git a/lib/icinga/checkable-script.cpp b/lib/icinga/checkable-script.cpp deleted file mode 100644 index a455440ca..000000000 --- a/lib/icinga/checkable-script.cpp +++ /dev/null @@ -1,33 +0,0 @@ -/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ - -#include "icinga/checkable.hpp" -#include "base/configobject.hpp" -#include "base/dictionary.hpp" -#include "base/function.hpp" -#include "base/functionwrapper.hpp" -#include "base/scriptframe.hpp" -#include "remote/apilistener.hpp" - -using namespace icinga; - -static void CheckableProcessCheckResult(const CheckResult::Ptr& cr) -{ - ScriptFrame *vframe = ScriptFrame::GetCurrentFrame(); - Checkable::Ptr self = vframe->Self; - REQUIRE_NOT_NULL(self); - - if (cr) { - auto api (ApiListener::GetInstance()); - - self->ProcessCheckResult(cr, api ? api->GetWaitGroup() : new StoppableWaitGroup()); - } -} - -Object::Ptr Checkable::GetPrototype() -{ - static Dictionary::Ptr prototype = new Dictionary({ - { "process_check_result", new Function("Checkable#process_check_result", CheckableProcessCheckResult, { "cr" }, false) } - }); - - return prototype; -} diff --git a/lib/icinga/checkable.cpp b/lib/icinga/checkable.cpp index 13fd778a3..690630f31 100644 --- a/lib/icinga/checkable.cpp +++ b/lib/icinga/checkable.cpp @@ -12,7 +12,7 @@ using namespace icinga; -REGISTER_TYPE_WITH_PROTOTYPE(Checkable, Checkable::GetPrototype()); +REGISTER_TYPE(Checkable); INITIALIZE_ONCE(&Checkable::StaticInitialize); const std::map Checkable::m_FlappingStateFilterMap ({ diff --git a/lib/icinga/checkable.hpp b/lib/icinga/checkable.hpp index 310923ec8..eacfdb626 100644 --- a/lib/icinga/checkable.hpp +++ b/lib/icinga/checkable.hpp @@ -214,8 +214,6 @@ public: static int GetPendingChecks(); static void AquirePendingCheckSlot(int maxPendingChecks); - static Object::Ptr GetPrototype(); - protected: void Start(bool runtimeCreated) override; void OnConfigLoaded() override; From bc5db9834fb23c64a3eea651e66ead31c7ed25a0 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 26 May 2025 14:02:51 +0200 Subject: [PATCH 336/415] Drop `System#track_parents` DSL function No external user needs to manipulate the actual object dependency graphs. This was maybe introduced for debugging purposes at that time but if someone messes with this in prod - good luck with that. Oh, apart from that it's broken :( and doesn't track parents as its implies but children. --- lib/base/scriptutils.cpp | 7 ------- lib/base/scriptutils.hpp | 1 - 2 files changed, 8 deletions(-) diff --git a/lib/base/scriptutils.cpp b/lib/base/scriptutils.cpp index 9054dadd6..c12bb0169 100644 --- a/lib/base/scriptutils.cpp +++ b/lib/base/scriptutils.cpp @@ -11,7 +11,6 @@ #include "base/objectlock.hpp" #include "base/configtype.hpp" #include "base/application.hpp" -#include "base/dependencygraph.hpp" #include "base/initialize.hpp" #include "base/namespace.hpp" #include "config/configitem.hpp" @@ -49,7 +48,6 @@ REGISTER_SAFE_FUNCTION(System, basename, &Utility::BaseName, "path"); REGISTER_SAFE_FUNCTION(System, dirname, &Utility::DirName, "path"); REGISTER_SAFE_FUNCTION(System, getenv, &ScriptUtils::GetEnv, "value"); REGISTER_SAFE_FUNCTION(System, msi_get_component_path, &ScriptUtils::MsiGetComponentPathShim, "component"); -REGISTER_SAFE_FUNCTION(System, track_parents, &ScriptUtils::TrackParents, "child"); REGISTER_SAFE_FUNCTION(System, escape_shell_cmd, &Utility::EscapeShellCmd, "cmd"); REGISTER_SAFE_FUNCTION(System, escape_shell_arg, &Utility::EscapeShellArg, "arg"); #ifdef _WIN32 @@ -518,11 +516,6 @@ String ScriptUtils::MsiGetComponentPathShim(const String& component) #endif /* _WIN32 */ } -Array::Ptr ScriptUtils::TrackParents(const Object::Ptr& child) -{ - return Array::FromVector(DependencyGraph::GetChildren(dynamic_pointer_cast(child))); -} - double ScriptUtils::Ptr(const Object::Ptr& object) { return reinterpret_cast(object.get()); diff --git a/lib/base/scriptutils.hpp b/lib/base/scriptutils.hpp index 7bd3e8b9d..a16d46f74 100644 --- a/lib/base/scriptutils.hpp +++ b/lib/base/scriptutils.hpp @@ -39,7 +39,6 @@ public: static Array::Ptr GetObjects(const Type::Ptr& type); static void Assert(const Value& arg); static String MsiGetComponentPathShim(const String& component); - static Array::Ptr TrackParents(const Object::Ptr& parent); static double Ptr(const Object::Ptr& object); static Value Glob(const std::vector& args); static Value GlobRecursive(const std::vector& args); From 1f4b0bdd461b24baedecf8b2b356932b41a5c658 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Thu, 5 Jun 2025 16:40:11 +0200 Subject: [PATCH 337/415] GHA: Fix rockylinux:9 by install compression libs Since recently, rockylinux:9 failed with linker errors against bz2, lzma and zstd. Installing the relevant devel packages fixed the builds. For reasons, cmake has started to add the -lbz2, -llzma, and -lzstd linker flags. Since Icinga 2 usually does not need those, the relevant devel packages were not installed. This may be due to some other transitively linked dependency. > 2025-06-05T13:12:59.5866282Z : && /usr/lib64/ccache/c++ -O2 -flto=auto -ffat-lto-objects -fexceptions -g -grecord-gcc-switches -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -Wp,-D_GLIBCXX_ASSERTIONS -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -fstack-protector-strong -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -m64 -march=x86-64-v2 -mtune=generic -fasynchronous-unwind-tables -fstack-clash-protection -fcf-protection -Wsuggest-override -Wrange-loop-construct -g -pthread -Winvalid-pch -O2 -g -DNDEBUG -Wl,-z,relro -Wl,--as-needed -Wl,-z,now -specs=/usr/lib/rpm/redhat/redhat-hardened-ld -specs=/usr/lib/rpm/redhat/redhat-annobin-cc1 -Wl,--export-dynamic -rdynamic third-party/mmatch/CMakeFiles/mmatch.dir/mmatch.c.o third-party/socketpair/CMakeFiles/socketpair.dir/socketpair.c.o lib/base/CMakeFiles/base.dir/application-version.cpp.o lib/base/CMakeFiles/base.dir/journaldlogger.cpp.o lib/base/CMakeFiles/base.dir/base_unity.cpp.o third-party/execvpe/CMakeFiles/execvpe.dir/execvpe.c.o lib/config/CMakeFiles/config.dir/config_lexer.cc.o lib/config/CMakeFiles/config.dir/config_parser.cc.o lib/config/CMakeFiles/config.dir/config_unity.cpp.o lib/remote/CMakeFiles/remote.dir/remote_unity.cpp.o plugins/CMakeFiles/check_nscp_api.dir/check_nscp_api.cpp.o -o Bin/RelWithDebInfo/check_nscp_api -Wl,-rpath,:::::::::::::::::::::::: -ldl /usr/lib64/libboost_coroutine.so.1.75.0 /usr/lib64/libboost_context.so.1.75.0 /usr/lib64/libboost_date_time.so.1.75.0 /usr/lib64/libboost_filesystem.so.1.75.0 /usr/lib64/libboost_iostreams.so.1.75.0 /usr/lib64/libboost_thread.so.1.75.0 /usr/lib64/libboost_system.so.1.75.0 /usr/lib64/libboost_program_options.so.1.75.0 /usr/lib64/libboost_regex.so.1.75.0 /usr/lib64/libssl.so /usr/lib64/libcrypto.so -lsystemd /lib64/libedit.so -ltermcap /usr/lib64/libboost_chrono.so.1.75.0 -lbz2 -llzma -lz -lzstd -licudata -licui18n -licuuc && : > 2025-06-05T13:12:59.5872347Z /usr/bin/ld: cannot find -lbz2 > 2025-06-05T13:12:59.5872577Z /usr/bin/ld: cannot find -llzma > 2025-06-05T13:12:59.5872802Z /usr/bin/ld: cannot find -lzstd > 2025-06-05T13:12:59.5873039Z collect2: error: ld returned 1 exit status Hopefully, this issue may resolve itself in the near future, but for the time being this satisfies our CI run on Rocky Linux 9. --- .github/workflows/linux.bash | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/linux.bash b/.github/workflows/linux.bash index 5cb1d6f97..9bf4a68bc 100755 --- a/.github/workflows/linux.bash +++ b/.github/workflows/linux.bash @@ -73,7 +73,7 @@ case "$DISTRO" in esac dnf install -y bison ccache cmake gcc-c++ flex ninja-build redhat-rpm-config \ - {boost,libedit,mariadb,ncurses,openssl,postgresql,systemd}-devel + {boost,bzip2,libedit,mariadb,ncurses,openssl,postgresql,systemd,xz,libzstd}-devel ;; esac From 5d11df1abfcebbf71cc717877905ac750b231a0d Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 20 May 2025 16:54:19 +0200 Subject: [PATCH 338/415] IcingaDB: Send the string representation of `comment#entry_type` to Redis --- lib/icingadb/icingadb-objects.cpp | 6 +++--- lib/icingadb/icingadb-utility.cpp | 15 +++++++++++++++ lib/icingadb/icingadb.hpp | 1 + 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index 56b74a3c1..da9b94ed3 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -1684,7 +1684,7 @@ bool IcingaDB::PrepareObject(const ConfigObject::Ptr& object, Dictionary::Ptr& a attributes->Set("author", comment->GetAuthor()); attributes->Set("text", comment->GetText()); - attributes->Set("entry_type", comment->GetEntryType()); + attributes->Set("entry_type", IcingaDB::CommentTypeToString(comment->GetEntryType())); attributes->Set("entry_time", TimestampToMilliseconds(comment->GetEntryTime())); attributes->Set("is_persistent", comment->GetPersistent()); attributes->Set("is_sticky", comment->GetSticky()); @@ -2300,7 +2300,7 @@ void IcingaDB::SendAddedComment(const Comment::Ptr& comment) "entry_time", Convert::ToString(TimestampToMilliseconds(comment->GetEntryTime())), "author", Utility::ValidateUTF8(comment->GetAuthor()), "comment", Utility::ValidateUTF8(comment->GetText()), - "entry_type", Convert::ToString(comment->GetEntryType()), + "entry_type", IcingaDB::CommentTypeToString(comment->GetEntryType()), "is_persistent", Convert::ToString((unsigned short)comment->GetPersistent()), "is_sticky", Convert::ToString((unsigned short)comment->GetSticky()), "event_id", CalcEventID("comment_add", comment), @@ -2372,7 +2372,7 @@ void IcingaDB::SendRemovedComment(const Comment::Ptr& comment) "entry_time", Convert::ToString(TimestampToMilliseconds(comment->GetEntryTime())), "author", Utility::ValidateUTF8(comment->GetAuthor()), "comment", Utility::ValidateUTF8(comment->GetText()), - "entry_type", Convert::ToString(comment->GetEntryType()), + "entry_type", IcingaDB::CommentTypeToString(comment->GetEntryType()), "is_persistent", Convert::ToString((unsigned short)comment->GetPersistent()), "is_sticky", Convert::ToString((unsigned short)comment->GetSticky()), "event_id", CalcEventID("comment_remove", comment), diff --git a/lib/icingadb/icingadb-utility.cpp b/lib/icingadb/icingadb-utility.cpp index 89e5a5031..a7ea8b47f 100644 --- a/lib/icingadb/icingadb-utility.cpp +++ b/lib/icingadb/icingadb-utility.cpp @@ -245,6 +245,21 @@ const char* IcingaDB::GetNotificationTypeByEnum(NotificationType type) VERIFY(!"Invalid notification type."); } +/** + * Converts the given comment type to its string representation. + * + * @ingroup icinga + */ +String IcingaDB::CommentTypeToString(CommentType type) +{ + switch (type) { + case CommentUser: return "comment"; + case CommentAcknowledgement: return "ack"; + default: + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid comment type specified")); + } +} + static const std::set propertiesBlacklistEmpty; String IcingaDB::HashValue(const Value& value) diff --git a/lib/icingadb/icingadb.hpp b/lib/icingadb/icingadb.hpp index af58a977d..34bad080d 100644 --- a/lib/icingadb/icingadb.hpp +++ b/lib/icingadb/icingadb.hpp @@ -174,6 +174,7 @@ private: static String GetObjectIdentifier(const ConfigObject::Ptr& object); static String CalcEventID(const char* eventType, const ConfigObject::Ptr& object, double eventTime = 0, NotificationType nt = NotificationType(0)); static const char* GetNotificationTypeByEnum(NotificationType type); + static String CommentTypeToString(CommentType type); static Dictionary::Ptr SerializeVars(const Dictionary::Ptr& vars); static Dictionary::Ptr SerializeDependencyEdgeState(const DependencyGroup::Ptr& dependencyGroup, const Dependency::Ptr& dep); static Dictionary::Ptr SerializeRedundancyGroupState(const Checkable::Ptr& child, const DependencyGroup::Ptr& redundancyGroup); From 953a2e2e9695561d790a61fb969c3e91ace4bab1 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 20 May 2025 17:10:33 +0200 Subject: [PATCH 339/415] Merge `{host,service}::StateTypeToString()` & drop unused `StateTypeFromString()` --- lib/compat/compatlogger.cpp | 12 ++++++------ lib/db_ido/dbevents.cpp | 4 ++-- lib/icinga/checkable.cpp | 6 ++++++ lib/icinga/checkable.hpp | 2 ++ lib/icinga/host.cpp | 16 ---------------- lib/icinga/host.hpp | 3 --- lib/icinga/service.cpp | 16 ---------------- lib/icinga/service.hpp | 3 --- 8 files changed, 16 insertions(+), 46 deletions(-) diff --git a/lib/compat/compatlogger.cpp b/lib/compat/compatlogger.cpp index 95ca830e1..60b47556e 100644 --- a/lib/compat/compatlogger.cpp +++ b/lib/compat/compatlogger.cpp @@ -130,7 +130,7 @@ void CompatLogger::CheckResultHandler(const Checkable::Ptr& checkable, const Che << host->GetName() << ";" << service->GetShortName() << ";" << Service::StateToString(service->GetState()) << ";" - << Service::StateTypeToString(service->GetStateType()) << ";" + << Checkable::StateTypeToString(service->GetStateType()) << ";" << attempt_after << ";" << output << "" << ""; @@ -140,7 +140,7 @@ void CompatLogger::CheckResultHandler(const Checkable::Ptr& checkable, const Che msgbuf << "HOST ALERT: " << host->GetName() << ";" << GetHostStateString(host) << ";" - << Host::StateTypeToString(host->GetStateType()) << ";" + << Checkable::StateTypeToString(host->GetStateType()) << ";" << attempt_after << ";" << output << "" << ""; @@ -413,14 +413,14 @@ void CompatLogger::EventCommandHandler(const Checkable::Ptr& checkable) << host->GetName() << ";" << service->GetShortName() << ";" << Service::StateToString(service->GetState()) << ";" - << Service::StateTypeToString(service->GetStateType()) << ";" + << Checkable::StateTypeToString(service->GetStateType()) << ";" << current_attempt << ";" << event_command_name; } else { msgbuf << "HOST EVENT HANDLER: " << host->GetName() << ";" << GetHostStateString(host) << ";" - << Host::StateTypeToString(host->GetStateType()) << ";" + << Checkable::StateTypeToString(host->GetStateType()) << ";" << current_attempt << ";" << event_command_name; } @@ -505,7 +505,7 @@ void CompatLogger::ReopenFile(bool rotate) msgbuf << "CURRENT HOST STATE: " << host->GetName() << ";" << GetHostStateString(host) << ";" - << Host::StateTypeToString(host->GetStateType()) << ";" + << Checkable::StateTypeToString(host->GetStateType()) << ";" << host->GetCheckAttempt() << ";" << output << ""; @@ -526,7 +526,7 @@ void CompatLogger::ReopenFile(bool rotate) << host->GetName() << ";" << service->GetShortName() << ";" << Service::StateToString(service->GetState()) << ";" - << Service::StateTypeToString(service->GetStateType()) << ";" + << Checkable::StateTypeToString(service->GetStateType()) << ";" << service->GetCheckAttempt() << ";" << output << ""; diff --git a/lib/db_ido/dbevents.cpp b/lib/db_ido/dbevents.cpp index 8358824e7..cf80a0226 100644 --- a/lib/db_ido/dbevents.cpp +++ b/lib/db_ido/dbevents.cpp @@ -994,7 +994,7 @@ void DbEvents::AddCheckResultLogHistory(const Checkable::Ptr& checkable, const C << host->GetName() << ";" << service->GetShortName() << ";" << Service::StateToString(service->GetState()) << ";" - << Service::StateTypeToString(service->GetStateType()) << ";" + << Checkable::StateTypeToString(service->GetStateType()) << ";" << service->GetCheckAttempt() << ";" << output << "" << ""; @@ -1021,7 +1021,7 @@ void DbEvents::AddCheckResultLogHistory(const Checkable::Ptr& checkable, const C msgbuf << "HOST ALERT: " << host->GetName() << ";" << GetHostStateString(host) << ";" - << Host::StateTypeToString(host->GetStateType()) << ";" + << Checkable::StateTypeToString(host->GetStateType()) << ";" << host->GetCheckAttempt() << ";" << output << "" << ""; diff --git a/lib/icinga/checkable.cpp b/lib/icinga/checkable.cpp index 13fd778a3..242139bc9 100644 --- a/lib/icinga/checkable.cpp +++ b/lib/icinga/checkable.cpp @@ -322,3 +322,9 @@ void Checkable::CleanDeadlinedExecutions(const Timer * const&) } } } + +String Checkable::StateTypeToString(StateType type) +{ + return type == StateTypeSoft ? "SOFT" : "HARD"; +} + diff --git a/lib/icinga/checkable.hpp b/lib/icinga/checkable.hpp index 310923ec8..e4403dadd 100644 --- a/lib/icinga/checkable.hpp +++ b/lib/icinga/checkable.hpp @@ -106,6 +106,8 @@ public: void UpdateNextCheck(const MessageOrigin::Ptr& origin = nullptr); + static String StateTypeToString(StateType type); + bool HasBeenChecked() const; virtual bool IsStateOK(ServiceState state) const = 0; diff --git a/lib/icinga/host.cpp b/lib/icinga/host.cpp index 10cd4b445..35fb25537 100644 --- a/lib/icinga/host.cpp +++ b/lib/icinga/host.cpp @@ -227,22 +227,6 @@ String Host::StateToString(HostState state) } } -StateType Host::StateTypeFromString(const String& type) -{ - if (type == "SOFT") - return StateTypeSoft; - else - return StateTypeHard; -} - -String Host::StateTypeToString(StateType type) -{ - if (type == StateTypeSoft) - return "SOFT"; - else - return "HARD"; -} - bool Host::ResolveMacro(const String& macro, const CheckResult::Ptr&, Value *result) const { if (macro == "state") { diff --git a/lib/icinga/host.hpp b/lib/icinga/host.hpp index d0d6c1aa4..7cacd160f 100644 --- a/lib/icinga/host.hpp +++ b/lib/icinga/host.hpp @@ -45,9 +45,6 @@ public: static HostState StateFromString(const String& state); static String StateToString(HostState state); - static StateType StateTypeFromString(const String& state); - static String StateTypeToString(StateType state); - bool ResolveMacro(const String& macro, const CheckResult::Ptr& cr, Value *result) const override; void OnAllConfigLoaded() override; diff --git a/lib/icinga/service.cpp b/lib/icinga/service.cpp index a26512b77..acc6c89e1 100644 --- a/lib/icinga/service.cpp +++ b/lib/icinga/service.cpp @@ -195,22 +195,6 @@ String Service::StateToString(ServiceState state) } } -StateType Service::StateTypeFromString(const String& type) -{ - if (type == "SOFT") - return StateTypeSoft; - else - return StateTypeHard; -} - -String Service::StateTypeToString(StateType type) -{ - if (type == StateTypeSoft) - return "SOFT"; - else - return "HARD"; -} - bool Service::ResolveMacro(const String& macro, const CheckResult::Ptr& cr, Value *result) const { if (macro == "state") { diff --git a/lib/icinga/service.hpp b/lib/icinga/service.hpp index ac27c3d93..558f73c03 100644 --- a/lib/icinga/service.hpp +++ b/lib/icinga/service.hpp @@ -39,9 +39,6 @@ public: static ServiceState StateFromString(const String& state); static String StateToString(ServiceState state); - static StateType StateTypeFromString(const String& state); - static String StateTypeToString(StateType state); - static void EvaluateApplyRules(const Host::Ptr& host); void OnAllConfigLoaded() override; From ef1c0eb9b30c16156ac65ccfe5d8ba525c66886f Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 20 May 2025 17:14:35 +0200 Subject: [PATCH 340/415] IcingaDB: Set `state_type` to `hard` or `soft` and not int --- lib/icingadb/icingadb-objects.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index da9b94ed3..dab719940 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -1974,7 +1974,7 @@ void IcingaDB::SendStateChange(const ConfigObject::Ptr& object, const CheckResul "id", HashValue(rawId), "environment_id", m_EnvironmentId, "host_id", GetObjectIdentifier(host), - "state_type", Convert::ToString(type), + "state_type", Checkable::StateTypeToString(type).ToLower(), "soft_state", Convert::ToString(cr ? service ? Convert::ToLong(cr->GetState()) : Convert::ToLong(Host::CalculateState(cr->GetState())) : 99), "hard_state", Convert::ToString(hard_state), "check_attempt", Convert::ToString(checkable->GetCheckAttempt()), @@ -2954,7 +2954,7 @@ Dictionary::Ptr IcingaDB::SerializeState(const Checkable::Ptr& checkable) */ attrs->Set("id", id); attrs->Set("environment_id", m_EnvironmentId); - attrs->Set("state_type", checkable->HasBeenChecked() ? checkable->GetStateType() : StateTypeHard); + attrs->Set("state_type", Checkable::StateTypeToString(checkable->HasBeenChecked() ? checkable->GetStateType() : StateTypeHard).ToLower()); // TODO: last_hard/soft_state should be "previous". if (service) { From 7037b18b3463c54bfdb8fd25a03aea3b53c1d1c1 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 20 May 2025 17:17:23 +0200 Subject: [PATCH 341/415] IcingaDB: Send the int representation of `states` & `types` filter --- lib/icinga/notification.hpp | 10 ++++++++-- lib/icingadb/icingadb-objects.cpp | 8 ++++---- lib/icingadb/icingadb-utility.cpp | 29 +++++++++++++++++++++++++++++ lib/icingadb/icingadb.hpp | 2 ++ 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/lib/icinga/notification.hpp b/lib/icinga/notification.hpp index 8c5a5f4b1..21cc2cf7d 100644 --- a/lib/icinga/notification.hpp +++ b/lib/icinga/notification.hpp @@ -29,7 +29,9 @@ enum NotificationFilter StateFilterUnknown = 8, StateFilterUp = 16, - StateFilterDown = 32 + StateFilterDown = 32, + + StateFilterAll = StateFilterOK | StateFilterWarning | StateFilterCritical | StateFilterUnknown | StateFilterUp | StateFilterDown, }; /** @@ -47,7 +49,11 @@ enum NotificationType NotificationProblem = 32, NotificationRecovery = 64, NotificationFlappingStart = 128, - NotificationFlappingEnd = 256 + NotificationFlappingEnd = 256, + + NotificationTypeAll = NotificationDowntimeStart | NotificationDowntimeEnd | NotificationDowntimeRemoved | + NotificationCustom | NotificationAcknowledgement | NotificationProblem | NotificationRecovery | + NotificationFlappingStart | NotificationFlappingEnd, }; class NotificationCommand; diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index dab719940..a842dd7cb 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -1624,8 +1624,8 @@ bool IcingaDB::PrepareObject(const ConfigObject::Ptr& object, Dictionary::Ptr& a attributes->Set("email", user->GetEmail()); attributes->Set("pager", user->GetPager()); attributes->Set("notifications_enabled", user->GetEnableNotifications()); - attributes->Set("states", user->GetStates()); - attributes->Set("types", user->GetTypes()); + attributes->Set("states", StateFilterToRedisValue(user->GetStateFilter())); + attributes->Set("types", TypeFilterToRedisValue(user->GetTypeFilter())); if (user->GetPeriod()) attributes->Set("timeperiod_id", GetObjectIdentifier(user->GetPeriod())); @@ -1673,8 +1673,8 @@ bool IcingaDB::PrepareObject(const ConfigObject::Ptr& object, Dictionary::Ptr& a } attributes->Set("notification_interval", std::max(0.0, std::round(notification->GetInterval()))); - attributes->Set("states", notification->GetStates()); - attributes->Set("types", notification->GetTypes()); + attributes->Set("states", StateFilterToRedisValue(notification->GetStateFilter())); + attributes->Set("types", TypeFilterToRedisValue(notification->GetTypeFilter())); return true; } diff --git a/lib/icingadb/icingadb-utility.cpp b/lib/icingadb/icingadb-utility.cpp index a7ea8b47f..68c0a08ab 100644 --- a/lib/icingadb/icingadb-utility.cpp +++ b/lib/icingadb/icingadb-utility.cpp @@ -219,6 +219,35 @@ Dictionary::Ptr IcingaDB::SerializeRedundancyGroupState(const Checkable::Ptr& ch }; } +/** + * Converts the given filter to its Redis value representation. + * + * Within the Icinga 2 code base, if the states filter bitsets are set to -1, the filter will match on all states. + * However, since sending -1 to Redis would crash the Icinga DB daemon, as the "states" field is of type uint8, so + * the primary purpose of this function is to make sure that no values outside the valid range of 0-255 are sent to Redis. + * + * @param filter The filter to convert. + */ +int IcingaDB::StateFilterToRedisValue(int filter) +{ + return filter & StateFilterAll; +} + +/** + * Converts the given filter to its Redis value representation. + * + * Within the Icinga 2 code base, if the types filter bitsets are set to -1, the filter will match on all types. + * However, since sending -1 to Redis would crash the Icinga DB daemon, as the "types" field is of type uint16, so + * the primary purpose of this function is to make sure that no values outside the "types" field's valid range are + * sent to Redis. + * + * @param filter The filter to convert. + */ +int IcingaDB::TypeFilterToRedisValue(int filter) +{ + return filter & NotificationTypeAll; +} + const char* IcingaDB::GetNotificationTypeByEnum(NotificationType type) { switch (type) { diff --git a/lib/icingadb/icingadb.hpp b/lib/icingadb/icingadb.hpp index 34bad080d..33b6414c8 100644 --- a/lib/icingadb/icingadb.hpp +++ b/lib/icingadb/icingadb.hpp @@ -173,6 +173,8 @@ private: static String GetObjectIdentifier(const ConfigObject::Ptr& object); static String CalcEventID(const char* eventType, const ConfigObject::Ptr& object, double eventTime = 0, NotificationType nt = NotificationType(0)); + static int StateFilterToRedisValue(int filter); + static int TypeFilterToRedisValue(int filter); static const char* GetNotificationTypeByEnum(NotificationType type); static String CommentTypeToString(CommentType type); static Dictionary::Ptr SerializeVars(const Dictionary::Ptr& vars); From 76d5915b3fcdf4b6c20b348eae6116b16a06a114 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 20 May 2025 17:20:56 +0200 Subject: [PATCH 342/415] IcingaDB: Set `notification_histor#type` to its string representation So that Icinga DB (Go) daemon doesn't have to make the mappings again. --- lib/icingadb/icingadb-objects.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index a842dd7cb..ca29dc38e 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -2047,8 +2047,9 @@ void IcingaDB::SendSentNotification( auto usersAmount (users.size()); auto sendTs (TimestampToMilliseconds(sendTime)); + auto notificationTypeStr(GetNotificationTypeByEnum(type)); Array::Ptr rawId = new Array({m_EnvironmentId, notification->GetName()}); - rawId->Add(GetNotificationTypeByEnum(type)); + rawId->Add(notificationTypeStr); rawId->Add(sendTs); auto notificationHistoryId (HashValue(rawId)); @@ -2059,7 +2060,7 @@ void IcingaDB::SendSentNotification( "environment_id", m_EnvironmentId, "notification_id", GetObjectIdentifier(notification), "host_id", GetObjectIdentifier(host), - "type", Convert::ToString(type), + "type", notificationTypeStr, "state", Convert::ToString(cr ? service ? Convert::ToLong(cr->GetState()) : Convert::ToLong(Host::CalculateState(cr->GetState())) : 99), "previous_hard_state", Convert::ToString(cr ? service ? Convert::ToLong(cr->GetPreviousHardState()) : Convert::ToLong(Host::CalculateState(cr->GetPreviousHardState())) : 99), "author", Utility::ValidateUTF8(author), From fd1927115a5dc3cf99ae6e3acdba8ed2d3924756 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 20 May 2025 17:25:30 +0200 Subject: [PATCH 343/415] IcingaDB: Make `is_acknowledged` a bool & add `is_sticky_acknowledgement` field --- lib/icingadb/icingadb-objects.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index ca29dc38e..594334658 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -3018,7 +3018,9 @@ Dictionary::Ptr IcingaDB::SerializeState(const Checkable::Ptr& checkable) attrs->Set("is_reachable", checkable->IsReachable()); attrs->Set("is_flapping", checkable->IsFlapping()); - attrs->Set("is_acknowledged", checkable->GetAcknowledgement()); + attrs->Set("is_acknowledged", checkable->IsAcknowledged()); + attrs->Set("is_sticky_acknowledgement", checkable->GetAcknowledgement() == AcknowledgementSticky); + if (checkable->IsAcknowledged()) { Timestamp entry = 0; Comment::Ptr AckComment; From 186571ec9907a9e1f479640e6cdc58104f2156b2 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 21 May 2025 09:09:10 +0200 Subject: [PATCH 344/415] Refresh the `states` & `types` bitsets whenever `states` & `types` attrs change Since the types and states attributes are user configurable and allowed to change at runtime, we need to update the actual filter bitsets whenever these attributes change. Otherwise, the filter bitsets would be stale and not reflect their current state. --- lib/icinga/notification.cpp | 41 ++++++++++++++++++++++++++++++++----- lib/icinga/notification.hpp | 19 ++++++++++++++++- lib/icinga/notification.ti | 10 +++++++-- lib/icinga/user.cpp | 41 ++++++++++++++++++++++++++++++++----- lib/icinga/user.hpp | 19 ++++++++++++++++- lib/icinga/user.ti | 10 +++++++-- 6 files changed, 124 insertions(+), 16 deletions(-) diff --git a/lib/icinga/notification.cpp b/lib/icinga/notification.cpp index 8c4cced1b..9bb0d05fa 100644 --- a/lib/icinga/notification.cpp +++ b/lib/icinga/notification.cpp @@ -100,12 +100,13 @@ void Notification::StaticInitialize() m_TypeFilterMap["FlappingEnd"] = NotificationFlappingEnd; } -void Notification::OnConfigLoaded() +Notification::Notification() { - ObjectImpl::OnConfigLoaded(); - - SetTypeFilter(FilterArrayToInt(GetTypes(), GetTypeFilterMap(), ~0)); - SetStateFilter(FilterArrayToInt(GetStates(), GetStateFilterMap(), ~0)); + // If a notification is created without specifying the "types/states" attribute, the Set* methods won't be called, + // consequently the filter bitset will also be 0. Thus, we need to ensure that the type/state filter are + // initialized to the default values, which are all types and states enabled. + SetTypes(nullptr, false, Empty); + SetStates(nullptr, false, Empty); } void Notification::OnAllConfigLoaded() @@ -751,6 +752,36 @@ String Notification::NotificationHostStateToString(HostState state) } } +Array::Ptr Notification::GetTypes() const +{ + return m_Types.load(); +} + +void Notification::SetTypes(const Array::Ptr& value, bool suppress_events, const Value& cookie) +{ + m_Types.store(value); + // Ensure that the type filter is updated when the types attribute changes. + SetTypeFilter(FilterArrayToInt(value, GetTypeFilterMap(), ~0)); + if (!suppress_events) { + NotifyTypes(cookie); + } +} + +Array::Ptr Notification::GetStates() const +{ + return m_States.load(); +} + +void Notification::SetStates(const Array::Ptr& value, bool suppress_events, const Value& cookie) +{ + m_States.store(value); + // Ensure that the state filter is updated when the states attribute changes. + SetStateFilter(FilterArrayToInt(value, GetStateFilterMap(), ~0)); + if (!suppress_events) { + NotifyStates(cookie); + } +} + void Notification::Validate(int types, const ValidationUtils& utils) { ObjectImpl::Validate(types, utils); diff --git a/lib/icinga/notification.hpp b/lib/icinga/notification.hpp index 21cc2cf7d..0e2f50c4c 100644 --- a/lib/icinga/notification.hpp +++ b/lib/icinga/notification.hpp @@ -74,6 +74,8 @@ public: DECLARE_OBJECT(Notification); DECLARE_OBJECTNAME(Notification); + Notification(); + static void StaticInitialize(); intrusive_ptr GetCheckable() const; @@ -115,13 +117,28 @@ public: static const std::map& GetStateFilterMap(); static const std::map& GetTypeFilterMap(); - void OnConfigLoaded() override; void OnAllConfigLoaded() override; void Start(bool runtimeCreated) override; void Stop(bool runtimeRemoved) override; + Array::Ptr GetTypes() const override; + void SetTypes(const Array::Ptr& value, bool suppress_events, const Value& cookie) override; + + Array::Ptr GetStates() const override; + void SetStates(const Array::Ptr& value, bool suppress_events, const Value& cookie) override; + private: ObjectImpl::Ptr m_Checkable; + // These attributes represent the actual notification "types" and "states" attributes from the "notification.ti". + // However, since we want to ensure that the type and state bitsets are always in sync with those attributes, + // we need to override their setters, and this on the hand introduces another problem: The virtual setters are + // called from within the ObjectImpl constructor, which obviously violates the C++ standard [^1]. + // So, in order to avoid all this kind of mess, these two attributes have the "no_storage" flag set, and + // their getters/setters are pure virtual, which means this class has to provide the implementation of them. + // + // [^1]: https://isocpp.org/wiki/faq/strange-inheritance#calling-virtuals-from-ctors + AtomicOrLocked m_Types; + AtomicOrLocked m_States; bool CheckNotificationUserFilters(NotificationType type, const User::Ptr& user, bool force, bool reminder); diff --git a/lib/icinga/notification.ti b/lib/icinga/notification.ti index a8121ff6b..e8c4418cd 100644 --- a/lib/icinga/notification.ti +++ b/lib/icinga/notification.ti @@ -41,9 +41,15 @@ class Notification : CustomVarObject < NotificationNameComposer [config, signal_with_old_value] array(name(User)) users (UsersRaw); [config, signal_with_old_value] array(name(UserGroup)) user_groups (UserGroupsRaw); [config] Dictionary::Ptr times; - [config] array(Value) types; + [config, no_storage] array(Value) types { + get; + set; + }; [no_user_view, no_user_modify] int type_filter_real (TypeFilter); - [config] array(Value) states; + [config, no_storage] array(Value) states { + get; + set; + }; [no_user_view, no_user_modify] int state_filter_real (StateFilter); [config, no_user_modify, protected, required, navigation(host)] name(Host) host_name { navigate {{{ diff --git a/lib/icinga/user.cpp b/lib/icinga/user.cpp index 5c646f656..ec72e9f8e 100644 --- a/lib/icinga/user.cpp +++ b/lib/icinga/user.cpp @@ -12,12 +12,13 @@ using namespace icinga; REGISTER_TYPE(User); -void User::OnConfigLoaded() +User::User() { - ObjectImpl::OnConfigLoaded(); - - SetTypeFilter(FilterArrayToInt(GetTypes(), Notification::GetTypeFilterMap(), ~0)); - SetStateFilter(FilterArrayToInt(GetStates(), Notification::GetStateFilterMap(), ~0)); + // If a User is created without specifying the "types/states" attribute, the Set* methods won't be called, + // consequently the filter bitset will also be 0. Thus, we need to ensure that the type/state filter are + // initialized to the default values, which are all types and states enabled. + SetTypes(nullptr, false, Empty); + SetStates(nullptr, false, Empty); } void User::OnAllConfigLoaded() @@ -80,6 +81,36 @@ TimePeriod::Ptr User::GetPeriod() const return TimePeriod::GetByName(GetPeriodRaw()); } +Array::Ptr User::GetTypes() const +{ + return m_Types.load(); +} + +void User::SetTypes(const Array::Ptr& value, bool suppress_events, const Value& cookie) + { + m_Types.store(value); + // Ensure that the type filter is updated when the types attribute changes. + SetTypeFilter(FilterArrayToInt(value, Notification::GetTypeFilterMap(), ~0)); + if (!suppress_events) { + NotifyTypes(cookie); + } +} + +Array::Ptr User::GetStates() const +{ + return m_States.load(); +} + +void User::SetStates(const Array::Ptr& value, bool suppress_events, const Value& cookie) +{ + m_States.store(value); + // Ensure that the state filter is updated when the states attribute changes. + SetStateFilter(FilterArrayToInt(value, Notification::GetStateFilterMap(), ~0)); + if (!suppress_events) { + NotifyStates(cookie); + } +} + void User::ValidateStates(const Lazy& lvalue, const ValidationUtils& utils) { ObjectImpl::ValidateStates(lvalue, utils); diff --git a/lib/icinga/user.hpp b/lib/icinga/user.hpp index 14e59c2ce..5e87413cd 100644 --- a/lib/icinga/user.hpp +++ b/lib/icinga/user.hpp @@ -22,21 +22,38 @@ public: DECLARE_OBJECT(User); DECLARE_OBJECTNAME(User); + User(); + void AddGroup(const String& name); /* Notifications */ TimePeriod::Ptr GetPeriod() const; + Array::Ptr GetTypes() const override; + void SetTypes(const Array::Ptr& value, bool suppress_events, const Value& cookie) override; + + Array::Ptr GetStates() const override; + void SetStates(const Array::Ptr& value, bool suppress_events, const Value& cookie) override; + void ValidateStates(const Lazy& lvalue, const ValidationUtils& utils) override; void ValidateTypes(const Lazy& lvalue, const ValidationUtils& utils) override; protected: void Stop(bool runtimeRemoved) override; - void OnConfigLoaded() override; void OnAllConfigLoaded() override; private: mutable std::mutex m_UserMutex; + // These attributes represent the actual User "types" and "states" attributes from the "user.ti". + // However, since we want to ensure that the type and state bitsets are always in sync with those attributes, + // we need to override their setters, and this on the hand introduces another problem: The virtual setters are + // called from within the ObjectImpl constructor, which obviously violates the C++ standard [^1]. + // So, in order to avoid al this kind of mess, these two attributes have the "no_storage" flag set, and + // their getters/setters are pure virtual, which means this class has to provide the implementation of them. + // + // [^1]: https://isocpp.org/wiki/faq/strange-inheritance#calling-virtuals-from-ctors + AtomicOrLocked m_Types; + AtomicOrLocked m_States; }; } diff --git a/lib/icinga/user.ti b/lib/icinga/user.ti index 8b8c43a14..b42e1e826 100644 --- a/lib/icinga/user.ti +++ b/lib/icinga/user.ti @@ -29,9 +29,15 @@ class User : CustomVarObject }}} }; - [config] array(Value) types; + [config, no_storage] array(Value) types { + get; + set; + }; [no_user_view, no_user_modify] int type_filter_real (TypeFilter); - [config] array(Value) states; + [config, no_storage] array(Value) states { + get; + set; + }; [no_user_view, no_user_modify] int state_filter_real (StateFilter); [config] String email; From 9e65a8b63b9185877de0b008783e9462f916f91d Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 3 Jun 2025 18:15:03 +0200 Subject: [PATCH 345/415] Fix compiler warnings of missing `NotificationTypeAll` case --- lib/icingadb/icingadb-utility.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/icingadb/icingadb-utility.cpp b/lib/icingadb/icingadb-utility.cpp index 68c0a08ab..8fa0e338c 100644 --- a/lib/icingadb/icingadb-utility.cpp +++ b/lib/icingadb/icingadb-utility.cpp @@ -269,9 +269,9 @@ const char* IcingaDB::GetNotificationTypeByEnum(NotificationType type) return "flapping_start"; case NotificationFlappingEnd: return "flapping_end"; + default: + VERIFY(!"Invalid notification type."); } - - VERIFY(!"Invalid notification type."); } /** From 9cdbbf4edec875ec359c773b5058aedd78c53afe Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Thu, 5 Jun 2025 13:35:12 +0200 Subject: [PATCH 346/415] IcingaDB::CalcEventID: No milliseconds as eventTime CalcEventID's internal logic uses the TimestampToMilliseconds function to convert the given eventTime to milliseconds. Within this function, the timestamp is capped to prevent an overflow. On three occasions, the input timestamp given to CalcEventID had already been converted using TimestampToMilliseconds. The second TimestampToMilliseconds function then checked the value and always returned the capped maximum value. Consequently, CalcEventID returned the same hash value for different timestamps. This affected SendFlappingChange, SendAcknowledgementSet, and SendAcknowledgementCleared. For example, when two acknowledgments were created for the same service, the calculated event_id representing the history table row would be identical. Fixes #10465 --- lib/icingadb/icingadb-objects.cpp | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index 56b74a3c1..93e226a33 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -2452,17 +2452,17 @@ void IcingaDB::SendFlappingChange(const Checkable::Ptr& checkable, double change xAdd.emplace_back(GetObjectIdentifier(endpoint)); } - long long startTime; + double startTime; if (checkable->IsFlapping()) { - startTime = TimestampToMilliseconds(changeTime); + startTime = changeTime; xAdd.emplace_back("event_type"); xAdd.emplace_back("flapping_start"); xAdd.emplace_back("percent_state_change_start"); xAdd.emplace_back(Convert::ToString(checkable->GetFlappingCurrent())); } else { - startTime = TimestampToMilliseconds(flappingLastChange); + startTime = flappingLastChange; xAdd.emplace_back("event_type"); xAdd.emplace_back("flapping_end"); @@ -2472,12 +2472,14 @@ void IcingaDB::SendFlappingChange(const Checkable::Ptr& checkable, double change xAdd.emplace_back(Convert::ToString(checkable->GetFlappingCurrent())); } + long long startTs = TimestampToMilliseconds(startTime); + xAdd.emplace_back("start_time"); - xAdd.emplace_back(Convert::ToString(startTime)); + xAdd.emplace_back(Convert::ToString(startTs)); xAdd.emplace_back("event_id"); xAdd.emplace_back(CalcEventID(checkable->IsFlapping() ? "flapping_start" : "flapping_end", checkable, startTime)); xAdd.emplace_back("id"); - xAdd.emplace_back(HashValue(new Array({m_EnvironmentId, checkable->GetName(), startTime}))); + xAdd.emplace_back(HashValue(new Array({m_EnvironmentId, checkable->GetName(), startTs}))); m_HistoryBulker.ProduceOne(std::move(xAdd)); } @@ -2555,14 +2557,14 @@ void IcingaDB::SendAcknowledgementSet(const Checkable::Ptr& checkable, const Str xAdd.emplace_back(Convert::ToString(TimestampToMilliseconds(expiry))); } - long long setTime = TimestampToMilliseconds(changeTime); + long long setTs = TimestampToMilliseconds(changeTime); xAdd.emplace_back("set_time"); - xAdd.emplace_back(Convert::ToString(setTime)); + xAdd.emplace_back(Convert::ToString(setTs)); xAdd.emplace_back("event_id"); - xAdd.emplace_back(CalcEventID("ack_set", checkable, setTime)); + xAdd.emplace_back(CalcEventID("ack_set", checkable, changeTime)); xAdd.emplace_back("id"); - xAdd.emplace_back(HashValue(new Array({m_EnvironmentId, checkable->GetName(), setTime}))); + xAdd.emplace_back(HashValue(new Array({m_EnvironmentId, checkable->GetName(), setTs}))); m_HistoryBulker.ProduceOne(std::move(xAdd)); } @@ -2605,14 +2607,14 @@ void IcingaDB::SendAcknowledgementCleared(const Checkable::Ptr& checkable, const xAdd.emplace_back(GetObjectIdentifier(endpoint)); } - long long setTime = TimestampToMilliseconds(ackLastChange); + long long setTs = TimestampToMilliseconds(ackLastChange); xAdd.emplace_back("set_time"); - xAdd.emplace_back(Convert::ToString(setTime)); + xAdd.emplace_back(Convert::ToString(setTs)); xAdd.emplace_back("event_id"); - xAdd.emplace_back(CalcEventID("ack_clear", checkable, setTime)); + xAdd.emplace_back(CalcEventID("ack_clear", checkable, ackLastChange)); xAdd.emplace_back("id"); - xAdd.emplace_back(HashValue(new Array({m_EnvironmentId, checkable->GetName(), setTime}))); + xAdd.emplace_back(HashValue(new Array({m_EnvironmentId, checkable->GetName(), setTs}))); if (!removedBy.IsEmpty()) { xAdd.emplace_back("cleared_by"); From 1632dd5362fc0019c1f9902ca2fa7fe3f210fb56 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 21 Jan 2025 18:18:40 +0100 Subject: [PATCH 347/415] Icinga 2.14.4 --- CHANGELOG.md | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 62a295194..aeec785dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,70 @@ documentation before upgrading to a new release. Released closed milestones can be found on [GitHub](https://github.com/Icinga/icinga2/milestones?state=closed). +## 2.14.4 (2025-01-23) + +This bugfix release is focused on improving HA cluster stability and easing +troubleshooting of issues in this area. It also addresses several crashes, +in the core itself and both in Icinga DB and IDO (numbers out of range). +In addition, it fixes several other issues such as lost notifications +or TimePeriod/ScheduledDowntime exceeding specified date ranges. + +### Crash Fixes + +* Invalid `DateTime#format()` arguments in config and console on Windows Server 2016 and older. #10112 +* Downtime scheduling at runtime with non-existent trigger. #10049 +* Object creation at runtime during Icinga DB initialization. #10151 +* Comment on a service of a non-existent host. #9861 + +### Miscellaneous Bugfixes + +* Lost notifications after recovery outside the notification time period. #10187 +* TimePeriod/ScheduledDowntime exceeding specified date range. #9983 #10107 +* Clean up failure for obsolete Downtimes. #10062 +* ifw-api check command: use correct process-finished handler. #10140 +* Email notification scripts: strip 0x0D (CR) for a proper Content-Type. #10061 +* Several fixes and improvements of the code quality. #10066 #10214 #10254 #10263 #10264 + +### Cluster and API + +* Sync runtime objects in topological order to honor their dependencies. #10000 +* Make parallel config syncs more robust. #10013 +* After object creation via API fails, clean up properly for the next try. #10111 +* Close HTTPS connections properly to prevent leaks. #10005 #10006 +* Reduce the number of cluster messages in memory at the same time. #9991 #9999 #10210 +* Once a cluster connection shall be closed, stop communicating. #10213 #10221 +* Remove unnecessary blocking of semaphores. #9992 #9994 +* Reduce unnecessary cluster messages setting the next check time. #10011 + +### Icinga DB and IDO + +* IDO: fix object relations after aborted synchronization. #10065 +* Icinga DB, IDO: limit all timestamps to four year digits. #10058 #10059 +* Icinga DB: limit execution\_time and latency (milliseconds) to database schema. #10060 + +### Troubleshooting + +* Add `/v1/debug/malloc_info` which calls `malloc_info(3)` if available. #10015 +* Add log messages about own network I/O. #9993 #10141 #10207 +* Several fixes and improvements of log messages. #9997 #10021 #10209 + +### Windows + +* Update OpenSSL shipped on Windows to v3.0.15. #10170 +* Update Boost shipped on Windows to v1.86. #10114 +* Support CMake v3.29. #10037 +* Don't require to build .msi as admin. #10137 +* Build configuration scripts: allow custom `$CMAKE_ARGS`. #10312 + +### Documentation + +* Distributed Monitoring: add section "External CA/PKI". #9825 +* Explain how to enable/disable debug logging on the fly. #9981 +* Update supported OS versions and repository configuration. #10064 #10090 #10120 #10135 #10136 #10205 +* Several fixes and improvements. #9960 #10050 #10071 #10156 #10194 +* Replace broken links. #10115 #10118 #10282 +* Fix typographical and similarly trivial errors. #9953 #9967 #10056 #10116 #10152 #10153 #10204 + ## 2.14.3 (2024-11-12) This security release fixes a TLS certificate validation bypass. From 8c7ed2facac05f859c0e1c2f5a7bf985a9a606a2 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Tue, 4 Feb 2025 15:28:41 +0100 Subject: [PATCH 348/415] Release v2.14.5 --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aeec785dd..588c596db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,26 @@ documentation before upgrading to a new release. Released closed milestones can be found on [GitHub](https://github.com/Icinga/icinga2/milestones?state=closed). +## 2.14.5 (2025-02-06) + +This release fixes a regression introduced in 2.14.4 that caused the `icinga2 node setup`, +`icinga2 node wizard`, and `icinga2 pki request` commands to fail if a certificate was +requested from a node that has to forward the request to another node for signing. +Additionally, it fixes a small bug in the performance data normalization and includes +various documentation improvements. + +### Bug Fixes + +* Don't close anonymous connections before sending the response for a certificate request #10337 +* Performance data: Don't discard min/max values even if crit/warn thresholds aren’t given #10339 + +### Documentation + +* Document the -X option for the mail-host-notification and mail-service-notification commands #10335 +* Include Nagios in the migration docs #10324 +* Remove RHEL 7 from installation instructions #10334 +* Add instructions for installing build dependencies on Windows Server #10336 + ## 2.14.4 (2025-01-23) This bugfix release is focused on improving HA cluster stability and easing From 404136141b562dce2fa3a3927c33fb26074ccf60 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Tue, 20 May 2025 16:45:41 +0200 Subject: [PATCH 349/415] Icinga 2.14.6 --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 588c596db..373f60241 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,19 @@ documentation before upgrading to a new release. Released closed milestones can be found on [GitHub](https://github.com/Icinga/icinga2/milestones?state=closed). +## 2.14.6 (2025-05-27) + +This security release fixes a critical issue in the certificate renewal logic in Icinga 2, which +might incorrectly renew an invalid certificate. However, only nodes with access to the Icinga CA +private key running with OpenSSL older than version 1.1.0 (released in 2016) are vulnerable. So this +typically affects Icinga 2 masters running on operating systems like RHEL 7 and Amazon Linux 2. + +* CVE-2025-48057: Prevent invalid certificates from being renewed with OpenSSL older than v1.1.0. +* Fix use-after-free in VerifyCertificate(): Additionally, a use-after-free was found in the same + function which is fixed as well, but in case it is triggered, typically only a wrong error code + may be shown in a log message. +* Windows: Update OpenSSL shipped on Windows to v3.0.16. + ## 2.14.5 (2025-02-06) This release fixes a regression introduced in 2.14.4 that caused the `icinga2 node setup`, From 2e640bc7e23fc648cce834b539cfc039cbf06eb1 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Tue, 20 May 2025 16:45:41 +0200 Subject: [PATCH 350/415] Icinga 2.13.12 --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 373f60241..1b115cbb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -340,6 +340,20 @@ Add `linux_netdev` check command. #9045 * Several code quality improvements. #8815 #9106 #9250 #9508 #9517 #9537 #9594 #9605 #9606 #9641 #9658 #9702 #9717 #9738 +## 2.13.12 (2025-05-27) + +This security release fixes a critical issue in the certificate renewal logic in Icinga 2, which +might incorrectly renew an invalid certificate. However, only nodes with access to the Icinga CA +private key running with OpenSSL older than version 1.1.0 (released in 2016) are vulnerable. So this +typically affects Icinga 2 masters running on operating systems like RHEL 7 and Amazon Linux 2. + +* CVE-2025-48057: Prevent invalid certificates from being renewed with OpenSSL older than v1.1.0. +* Fix use-after-free in VerifyCertificate(): Additionally, a use-after-free was found in the same + function which is fixed as well, but in case it is triggered, typically only a wrong error code + may be shown in a log message. +* Windows: Update OpenSSL shipped on Windows to v3.0.16. +* Fix a failing test case on systems `time_t` is only 32 bits #10344. + ## 2.13.11 (2025-01-23) This bugfix release addresses several crashes, From da89233dbef8efaf68ed463f4c398ba791967103 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Wed, 5 Feb 2025 13:15:10 +0100 Subject: [PATCH 351/415] Mention #10343 in changelog for 2.14.5 The problem was only noticed after the changelog already got merged, hence this has to be added retroactively. --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1b115cbb2..94c553cf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -32,6 +32,7 @@ various documentation improvements. * Don't close anonymous connections before sending the response for a certificate request #10337 * Performance data: Don't discard min/max values even if crit/warn thresholds aren’t given #10339 +* Fix a failing test case on systems `time_t` is only 32 bits #10343 ### Documentation From 157e3750e304913e1277ab8ebdd09ab99c2db0a4 Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Wed, 11 Jun 2025 09:54:53 +0200 Subject: [PATCH 352/415] Add IsLockable method to WaitGroup --- lib/base/wait-group.cpp | 7 ++++++- lib/base/wait-group.hpp | 8 +++++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/base/wait-group.cpp b/lib/base/wait-group.cpp index 1e1ad00ee..6ad716605 100644 --- a/lib/base/wait-group.cpp +++ b/lib/base/wait-group.cpp @@ -26,6 +26,11 @@ void StoppableWaitGroup::unlock_shared() } } +bool StoppableWaitGroup::IsLockable() const +{ + return !m_Stopped.load(std::memory_order_relaxed); +} + /** * Disallow new shared locks, wait for all existing ones. */ @@ -33,6 +38,6 @@ void StoppableWaitGroup::Join() { std::unique_lock lock (m_Mutex); - m_Stopped = true; + m_Stopped.store(true, std::memory_order_relaxed); m_CV.wait(lock, [this] { return !m_SharedLocks; }); } diff --git a/lib/base/wait-group.hpp b/lib/base/wait-group.hpp index 5b4527011..618f07aec 100644 --- a/lib/base/wait-group.hpp +++ b/lib/base/wait-group.hpp @@ -3,6 +3,7 @@ #pragma once #include "base/object.hpp" +#include "base/atomic.hpp" #include #include #include @@ -22,6 +23,8 @@ public: virtual bool try_lock_shared() = 0; virtual void unlock_shared() = 0; + + virtual bool IsLockable() const = 0; }; /** @@ -42,13 +45,16 @@ public: bool try_lock_shared() override; void unlock_shared() override; + + bool IsLockable() const override; + void Join(); private: std::mutex m_Mutex; std::condition_variable m_CV; uint_fast32_t m_SharedLocks = 0; - bool m_Stopped = false; + Atomic m_Stopped = false; }; } From 00802ed9fadf626f95a0e1766b6919407db0ebfa Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Mon, 26 May 2025 10:01:05 +0200 Subject: [PATCH 353/415] Stop ApiListener::ListenerCoroutineProc() when Stop() is called --- lib/remote/apilistener.cpp | 49 ++++++++++++++++++++++++++++++++++++-- lib/remote/apilistener.hpp | 4 ++++ 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 5ba45b94f..d8f7b0888 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -368,6 +368,8 @@ void ApiListener::Stop(bool runtimeDeleted) m_Timer->Stop(true); m_RenewOwnCertTimer->Stop(true); + StopListener(); + m_WaitGroup->Join(); ObjectImpl::Stop(runtimeDeleted); @@ -486,13 +488,38 @@ bool ApiListener::AddListener(const String& node, const String& service) Log(LogInformation, "ApiListener") << "Started new listener on '[" << localEndpoint.address() << "]:" << localEndpoint.port() << "'"; - IoEngine::SpawnCoroutine(io, [this, acceptor](asio::yield_context yc) { ListenerCoroutineProc(yc, acceptor); }); + auto strand = Shared::Make(io); + + boost::signals2::scoped_connection closeSignal = m_OnListenerShutdown.connect([strand, acceptor]() { + boost::asio::post(*strand, [acceptor] { + try { + acceptor->close(); + } catch (const std::exception& ex) { + Log(LogCritical, "ApiListener") + << "Failed to close acceptor socket: " << ex.what(); + } + }); + }); + + IoEngine::SpawnCoroutine(*strand, [this, acceptor, closeSignal = std::move(closeSignal)](asio::yield_context yc) { + ListenerCoroutineProc(yc, acceptor); + }); UpdateStatusFile(localEndpoint); return true; } +/** + * Stops the listener(s). + */ +void ApiListener::StopListener() +{ + m_OnListenerShutdown(); + + m_ListenerWaitGroup->Join(); +} + void ApiListener::ListenerCoroutineProc(boost::asio::yield_context yc, const Shared::Ptr& server) { namespace asio = boost::asio; @@ -506,7 +533,14 @@ void ApiListener::ListenerCoroutineProc(boost::asio::yield_context yc, const Sha lastModified = Utility::GetFileCreationTime(crlPath); } - for (;;) { + std::shared_lock wgLock(*m_ListenerWaitGroup, std::try_to_lock); + if (!wgLock) { + Log(LogCritical, "ApiListener") + << "Could not lock the listener wait group."; + return; + } + + while (server->is_open()) { try { asio::ip::tcp::socket socket (io); @@ -546,6 +580,12 @@ void ApiListener::ListenerCoroutineProc(boost::asio::yield_context yc, const Sha NewClientHandler(yc, strand, sslConn, String(), RoleServer); }); } catch (const std::exception& ex) { + auto se (dynamic_cast(&ex)); + + if (se && se->code() == boost::asio::error::operation_aborted) { + return; + } + Log(LogCritical, "ApiListener") << "Cannot accept new connection: " << ex.what(); } @@ -828,6 +868,11 @@ void ApiListener::NewClientHandlerInternal( throw; } + std::shared_lock wgLock(*m_ListenerWaitGroup, std::try_to_lock); + if (!wgLock) { + return; + } + if (ctype == ClientJsonRpc) { Log(LogNotice, "ApiListener", "New JSON-RPC client"); diff --git a/lib/remote/apilistener.hpp b/lib/remote/apilistener.hpp index 4fef5ba7d..82137fa32 100644 --- a/lib/remote/apilistener.hpp +++ b/lib/remote/apilistener.hpp @@ -191,12 +191,16 @@ private: static ApiListener::Ptr m_Instance; static std::atomic m_UpdatedObjectAuthority; + boost::signals2::signal m_OnListenerShutdown; + StoppableWaitGroup::Ptr m_ListenerWaitGroup = new StoppableWaitGroup(); + void ApiTimerHandler(); void ApiReconnectTimerHandler(); void CleanupCertificateRequestsTimerHandler(); void CheckApiPackageIntegrity(); bool AddListener(const String& node, const String& service); + void StopListener(); void AddConnection(const Endpoint::Ptr& endpoint); void NewClientHandler( From 33777f6f3f3a8fa94d462d4e4b561acc0360034a Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Fri, 6 Jun 2025 09:53:03 +0200 Subject: [PATCH 354/415] Disconnect JSON-RPC clients on ApiListner::Stop() --- lib/remote/apilistener.cpp | 19 ++++++++++++++++++- lib/remote/apilistener.hpp | 1 + lib/remote/endpoint.cpp | 2 +- lib/remote/jsonrpcconnection.cpp | 15 ++++++++++----- lib/remote/jsonrpcconnection.hpp | 8 ++++++-- 5 files changed, 36 insertions(+), 9 deletions(-) diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index d8f7b0888..9285f747f 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -370,7 +370,10 @@ void ApiListener::Stop(bool runtimeDeleted) StopListener(); + DisconnectJsonRpcConnections(); + m_WaitGroup->Join(); + ObjectImpl::Stop(runtimeDeleted); Log(LogInformation, "ApiListener") @@ -891,7 +894,7 @@ void ApiListener::NewClientHandlerInternal( return; } - JsonRpcConnection::Ptr aclient = new JsonRpcConnection(identity, verify_ok, client, role); + JsonRpcConnection::Ptr aclient = new JsonRpcConnection(m_WaitGroup, identity, verify_ok, client, role); if (endpoint) { endpoint->AddClient(aclient); @@ -1805,6 +1808,20 @@ std::set ApiListener::GetAnonymousClients() const return m_AnonymousClients; } +void ApiListener::DisconnectJsonRpcConnections() +{ + for (auto endpoint : ConfigType::GetObjectsByType()) { + for (const auto& client : endpoint->GetClients()) { + client->Disconnect(); + } + } + + std::unique_lock lock(m_AnonymousClientsLock); + for (const auto & client : m_AnonymousClients){ + client->Disconnect(); + } +} + void ApiListener::AddHttpClient(const HttpServerConnection::Ptr& aclient) { std::unique_lock lock(m_HttpClientsLock); diff --git a/lib/remote/apilistener.hpp b/lib/remote/apilistener.hpp index 82137fa32..f278c2e9b 100644 --- a/lib/remote/apilistener.hpp +++ b/lib/remote/apilistener.hpp @@ -114,6 +114,7 @@ public: bool AddAnonymousClient(const JsonRpcConnection::Ptr& aclient); void RemoveAnonymousClient(const JsonRpcConnection::Ptr& aclient); std::set GetAnonymousClients() const; + void DisconnectJsonRpcConnections(); void AddHttpClient(const HttpServerConnection::Ptr& aclient); void RemoveHttpClient(const HttpServerConnection::Ptr& aclient); diff --git a/lib/remote/endpoint.cpp b/lib/remote/endpoint.cpp index e534fc178..55ab68f12 100644 --- a/lib/remote/endpoint.cpp +++ b/lib/remote/endpoint.cpp @@ -60,7 +60,7 @@ void Endpoint::RemoveClient(const JsonRpcConnection::Ptr& client) std::unique_lock lock(m_ClientsLock); m_Clients.erase(client); - Log(LogWarning, "ApiListener") + Log(LogInformation, "ApiListener") << "Removing API client for endpoint '" << GetName() << "'. " << m_Clients.size() << " API clients left."; SetConnecting(false); diff --git a/lib/remote/jsonrpcconnection.cpp b/lib/remote/jsonrpcconnection.cpp index 889d4452c..a84f98d9f 100644 --- a/lib/remote/jsonrpcconnection.cpp +++ b/lib/remote/jsonrpcconnection.cpp @@ -29,17 +29,17 @@ REGISTER_APIFUNCTION(SetLogPosition, log, &SetLogPositionHandler); static RingBuffer l_TaskStats (15 * 60); -JsonRpcConnection::JsonRpcConnection(const String& identity, bool authenticated, +JsonRpcConnection::JsonRpcConnection(const WaitGroup::Ptr& waitGroup, const String& identity, bool authenticated, const Shared::Ptr& stream, ConnectionRole role) - : JsonRpcConnection(identity, authenticated, stream, role, IoEngine::Get().GetIoContext()) + : JsonRpcConnection(waitGroup, identity, authenticated, stream, role, IoEngine::Get().GetIoContext()) { } -JsonRpcConnection::JsonRpcConnection(const String& identity, bool authenticated, +JsonRpcConnection::JsonRpcConnection(const WaitGroup::Ptr& waitGroup, const String& identity, bool authenticated, const Shared::Ptr& stream, ConnectionRole role, boost::asio::io_context& io) : m_Identity(identity), m_Authenticated(authenticated), m_Stream(stream), m_Role(role), m_Timestamp(Utility::GetTime()), m_Seen(Utility::GetTime()), m_IoStrand(io), - m_OutgoingMessagesQueued(io), m_WriterDone(io), m_ShuttingDown(false), + m_OutgoingMessagesQueued(io), m_WriterDone(io), m_ShuttingDown(false), m_WaitGroup(waitGroup), m_CheckLivenessTimer(io), m_HeartbeatTimer(io) { if (authenticated) @@ -284,7 +284,7 @@ void JsonRpcConnection::Disconnect() ApiListener::GetInstance()->RemoveAnonymousClient(this); } - Log(LogWarning, "JsonRpcConnection") + Log(LogInformation, "JsonRpcConnection") << "API client disconnected for identity '" << m_Identity << "'"; }); } @@ -303,6 +303,11 @@ void JsonRpcConnection::Disconnect() */ void JsonRpcConnection::MessageHandler(const Dictionary::Ptr& message) { + std::shared_lock wgLock(*m_WaitGroup, std::try_to_lock); + if (!wgLock) { + return; + } + if (m_Endpoint && message->Contains("ts")) { double ts = message->Get("ts"); diff --git a/lib/remote/jsonrpcconnection.hpp b/lib/remote/jsonrpcconnection.hpp index 826d3b46a..df846527a 100644 --- a/lib/remote/jsonrpcconnection.hpp +++ b/lib/remote/jsonrpcconnection.hpp @@ -8,6 +8,7 @@ #include "base/atomic.hpp" #include "base/io-engine.hpp" #include "base/tlsstream.hpp" +#include "base/wait-group.hpp" #include "base/timer.hpp" #include "base/workqueue.hpp" #include @@ -43,7 +44,8 @@ class JsonRpcConnection final : public Object public: DECLARE_PTR_TYPEDEFS(JsonRpcConnection); - JsonRpcConnection(const String& identity, bool authenticated, const Shared::Ptr& stream, ConnectionRole role); + JsonRpcConnection(const WaitGroup::Ptr& waitgroup, const String& identity, bool authenticated, + const Shared::Ptr& stream, ConnectionRole role); void Start(); @@ -78,9 +80,11 @@ private: AsioEvent m_OutgoingMessagesQueued; AsioEvent m_WriterDone; Atomic m_ShuttingDown; + WaitGroup::Ptr m_WaitGroup; boost::asio::deadline_timer m_CheckLivenessTimer, m_HeartbeatTimer; - JsonRpcConnection(const String& identity, bool authenticated, const Shared::Ptr& stream, ConnectionRole role, boost::asio::io_context& io); + JsonRpcConnection(const WaitGroup::Ptr& waitgroup, const String& identity, bool authenticated, + const Shared::Ptr& stream, ConnectionRole role, boost::asio::io_context& io); void HandleIncomingMessages(boost::asio::yield_context yc); void WriteOutgoingMessages(boost::asio::yield_context yc); From 82bb636d2b9f4cb92a871f45a1bc5225f7af8cc5 Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Wed, 11 Jun 2025 10:28:16 +0200 Subject: [PATCH 355/415] Use WaitGroup to wait for or abort HTTP requests The wait group gets passed to HttpServerConnection, then down to the HttpHandlers. For those handlers that modify the program state, the wait group is locked so ApiListener will wait on Stop() for the request to complete. If the request iterates over config objects, a further check on the state of the wait group is added to abort early and not delay program shutdown. In that case, 503 responses will be sent to the client. Additionally, in HttpServerConnection, no further requests than the one already started will be allowed once the wait group is joining. --- lib/remote/actionshandler.cpp | 22 ++++++++++++++++++++++ lib/remote/actionshandler.hpp | 1 + lib/remote/apilistener.cpp | 2 +- lib/remote/configfileshandler.cpp | 1 + lib/remote/configfileshandler.hpp | 1 + lib/remote/configpackageshandler.cpp | 1 + lib/remote/configpackageshandler.hpp | 1 + lib/remote/configstageshandler.cpp | 1 + lib/remote/configstageshandler.hpp | 1 + lib/remote/consolehandler.cpp | 1 + lib/remote/consolehandler.hpp | 1 + lib/remote/createobjecthandler.cpp | 7 +++++++ lib/remote/createobjecthandler.hpp | 1 + lib/remote/deleteobjecthandler.cpp | 24 ++++++++++++++++++++++++ lib/remote/deleteobjecthandler.hpp | 1 + lib/remote/eventshandler.cpp | 1 + lib/remote/eventshandler.hpp | 1 + lib/remote/httphandler.cpp | 3 ++- lib/remote/httphandler.hpp | 2 ++ lib/remote/httpserverconnection.cpp | 15 ++++++++------- lib/remote/httpserverconnection.hpp | 9 ++++++--- lib/remote/infohandler.cpp | 1 + lib/remote/infohandler.hpp | 1 + lib/remote/mallocinfohandler.cpp | 1 + lib/remote/mallocinfohandler.hpp | 1 + lib/remote/modifyobjecthandler.cpp | 20 ++++++++++++++++++++ lib/remote/modifyobjecthandler.hpp | 1 + lib/remote/objectqueryhandler.cpp | 1 + lib/remote/objectqueryhandler.hpp | 1 + lib/remote/statushandler.cpp | 1 + lib/remote/statushandler.hpp | 1 + lib/remote/templatequeryhandler.cpp | 1 + lib/remote/templatequeryhandler.hpp | 1 + lib/remote/typequeryhandler.cpp | 1 + lib/remote/typequeryhandler.hpp | 1 + lib/remote/variablequeryhandler.cpp | 1 + lib/remote/variablequeryhandler.hpp | 1 + 37 files changed, 120 insertions(+), 12 deletions(-) diff --git a/lib/remote/actionshandler.cpp b/lib/remote/actionshandler.cpp index cd16c2bc0..5ae5fdc80 100644 --- a/lib/remote/actionshandler.cpp +++ b/lib/remote/actionshandler.cpp @@ -16,6 +16,7 @@ thread_local ApiUser::Ptr ActionsHandler::AuthenticatedApiUser; REGISTER_URLHANDLER("/v1/actions", ActionsHandler); bool ActionsHandler::HandleRequest( + const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, @@ -88,7 +89,28 @@ bool ActionsHandler::HandleRequest( if (params) verbose = HttpUtility::GetLastParameter(params, "verbose"); + std::shared_lock wgLock{*waitGroup, std::try_to_lock}; + if (!wgLock) { + HttpUtility::SendJsonError(response, params, 503, "Shutting down."); + return true; + } + for (ConfigObject::Ptr obj : objs) { + if (!waitGroup->IsLockable()) { + if (wgLock) { + wgLock.unlock(); + } + + results.emplace_back(new Dictionary({ + { "type", obj->GetReflectionType()->GetName() }, + { "name", obj->GetName() }, + { "code", 503 }, + { "status", "Action skipped: Shutting down."} + })); + + continue; + } + try { results.emplace_back(action->Invoke(obj, params)); } catch (const std::exception& ex) { diff --git a/lib/remote/actionshandler.hpp b/lib/remote/actionshandler.hpp index ca662caba..fbf716797 100644 --- a/lib/remote/actionshandler.hpp +++ b/lib/remote/actionshandler.hpp @@ -16,6 +16,7 @@ public: static thread_local ApiUser::Ptr AuthenticatedApiUser; bool HandleRequest( + const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 9285f747f..547eadc7f 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -917,7 +917,7 @@ void ApiListener::NewClientHandlerInternal( } else { Log(LogNotice, "ApiListener", "New HTTP client"); - HttpServerConnection::Ptr aclient = new HttpServerConnection(identity, verify_ok, client); + HttpServerConnection::Ptr aclient = new HttpServerConnection(m_WaitGroup, identity, verify_ok, client); AddHttpClient(aclient); aclient->Start(); shutdownSslConn.Cancel(); diff --git a/lib/remote/configfileshandler.cpp b/lib/remote/configfileshandler.cpp index 779ecd198..6c390e804 100644 --- a/lib/remote/configfileshandler.cpp +++ b/lib/remote/configfileshandler.cpp @@ -14,6 +14,7 @@ using namespace icinga; REGISTER_URLHANDLER("/v1/config/files", ConfigFilesHandler); bool ConfigFilesHandler::HandleRequest( + const WaitGroup::Ptr&, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/configfileshandler.hpp b/lib/remote/configfileshandler.hpp index ea48b1ef4..a8826d8c1 100644 --- a/lib/remote/configfileshandler.hpp +++ b/lib/remote/configfileshandler.hpp @@ -14,6 +14,7 @@ public: DECLARE_PTR_TYPEDEFS(ConfigFilesHandler); bool HandleRequest( + const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/configpackageshandler.cpp b/lib/remote/configpackageshandler.cpp index 98b326890..7987092bc 100644 --- a/lib/remote/configpackageshandler.cpp +++ b/lib/remote/configpackageshandler.cpp @@ -11,6 +11,7 @@ using namespace icinga; REGISTER_URLHANDLER("/v1/config/packages", ConfigPackagesHandler); bool ConfigPackagesHandler::HandleRequest( + const WaitGroup::Ptr&, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/configpackageshandler.hpp b/lib/remote/configpackageshandler.hpp index 0a05ea10a..2bae0e265 100644 --- a/lib/remote/configpackageshandler.hpp +++ b/lib/remote/configpackageshandler.hpp @@ -14,6 +14,7 @@ public: DECLARE_PTR_TYPEDEFS(ConfigPackagesHandler); bool HandleRequest( + const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/configstageshandler.cpp b/lib/remote/configstageshandler.cpp index edbb767e5..0dee5f25f 100644 --- a/lib/remote/configstageshandler.cpp +++ b/lib/remote/configstageshandler.cpp @@ -15,6 +15,7 @@ REGISTER_URLHANDLER("/v1/config/stages", ConfigStagesHandler); std::atomic ConfigStagesHandler::m_RunningPackageUpdates (false); bool ConfigStagesHandler::HandleRequest( + const WaitGroup::Ptr&, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/configstageshandler.hpp b/lib/remote/configstageshandler.hpp index 88f248c8f..a26ddc49c 100644 --- a/lib/remote/configstageshandler.hpp +++ b/lib/remote/configstageshandler.hpp @@ -15,6 +15,7 @@ public: DECLARE_PTR_TYPEDEFS(ConfigStagesHandler); bool HandleRequest( + const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/consolehandler.cpp b/lib/remote/consolehandler.cpp index 8ed36311c..c48821aae 100644 --- a/lib/remote/consolehandler.cpp +++ b/lib/remote/consolehandler.cpp @@ -54,6 +54,7 @@ static void EnsureFrameCleanupTimer() } bool ConsoleHandler::HandleRequest( + const WaitGroup::Ptr&, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/consolehandler.hpp b/lib/remote/consolehandler.hpp index df0d77d01..ba93d0001 100644 --- a/lib/remote/consolehandler.hpp +++ b/lib/remote/consolehandler.hpp @@ -23,6 +23,7 @@ public: DECLARE_PTR_TYPEDEFS(ConsoleHandler); bool HandleRequest( + const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/createobjecthandler.cpp b/lib/remote/createobjecthandler.cpp index 89977a3d3..119be1cd9 100644 --- a/lib/remote/createobjecthandler.cpp +++ b/lib/remote/createobjecthandler.cpp @@ -16,6 +16,7 @@ using namespace icinga; REGISTER_URLHANDLER("/v1/objects", CreateObjectHandler); bool CreateObjectHandler::HandleRequest( + const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, @@ -102,6 +103,12 @@ bool CreateObjectHandler::HandleRequest( return true; } + std::shared_lock wgLock{*waitGroup, std::try_to_lock}; + if (!wgLock) { + HttpUtility::SendJsonError(response, params, 503, "Shutting down."); + return true; + } + /* Object creation can cause multiple errors and optionally diagnostic information. * We can't use SendJsonError() here. */ diff --git a/lib/remote/createobjecthandler.hpp b/lib/remote/createobjecthandler.hpp index 4bcf21b55..3f6a705c2 100644 --- a/lib/remote/createobjecthandler.hpp +++ b/lib/remote/createobjecthandler.hpp @@ -14,6 +14,7 @@ public: DECLARE_PTR_TYPEDEFS(CreateObjectHandler); bool HandleRequest( + const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/deleteobjecthandler.cpp b/lib/remote/deleteobjecthandler.cpp index 150de99e0..54d31f13d 100644 --- a/lib/remote/deleteobjecthandler.cpp +++ b/lib/remote/deleteobjecthandler.cpp @@ -16,6 +16,7 @@ using namespace icinga; REGISTER_URLHANDLER("/v1/objects", DeleteObjectHandler); bool DeleteObjectHandler::HandleRequest( + const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, @@ -78,7 +79,30 @@ bool DeleteObjectHandler::HandleRequest( bool success = true; + std::shared_lock wgLock{*waitGroup, std::try_to_lock}; + if (!wgLock) { + HttpUtility::SendJsonError(response, params, 503, "Shutting down."); + return true; + } + for (ConfigObject::Ptr obj : objs) { + if (!waitGroup->IsLockable()) { + if (wgLock) { + wgLock.unlock(); + } + + results.emplace_back(new Dictionary({ + { "type", type->GetName() }, + { "name", obj->GetName() }, + { "code", 503 }, + { "status", "Action skipped: Shutting down."} + })); + + success = false; + + continue; + } + int code; String status; Array::Ptr errors = new Array(); diff --git a/lib/remote/deleteobjecthandler.hpp b/lib/remote/deleteobjecthandler.hpp index 19a46e475..0f9643277 100644 --- a/lib/remote/deleteobjecthandler.hpp +++ b/lib/remote/deleteobjecthandler.hpp @@ -14,6 +14,7 @@ public: DECLARE_PTR_TYPEDEFS(DeleteObjectHandler); bool HandleRequest( + const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/eventshandler.cpp b/lib/remote/eventshandler.cpp index 897398d4a..2cbee92f3 100644 --- a/lib/remote/eventshandler.cpp +++ b/lib/remote/eventshandler.cpp @@ -40,6 +40,7 @@ const std::map l_EventTypes ({ const String l_ApiQuery (""); bool EventsHandler::HandleRequest( + const WaitGroup::Ptr&, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/eventshandler.hpp b/lib/remote/eventshandler.hpp index c823415d3..49229733a 100644 --- a/lib/remote/eventshandler.hpp +++ b/lib/remote/eventshandler.hpp @@ -15,6 +15,7 @@ public: DECLARE_PTR_TYPEDEFS(EventsHandler); bool HandleRequest( + const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/httphandler.cpp b/lib/remote/httphandler.cpp index f67df4c69..79571d760 100644 --- a/lib/remote/httphandler.cpp +++ b/lib/remote/httphandler.cpp @@ -47,6 +47,7 @@ void HttpHandler::Register(const Url::Ptr& url, const HttpHandler::Ptr& handler) } void HttpHandler::ProcessRequest( + const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, @@ -108,7 +109,7 @@ void HttpHandler::ProcessRequest( */ try { for (const HttpHandler::Ptr& handler : handlers) { - if (handler->HandleRequest(stream, user, request, url, response, params, yc, server)) { + if (handler->HandleRequest(waitGroup, stream, user, request, url, response, params, yc, server)) { processed = true; break; } diff --git a/lib/remote/httphandler.hpp b/lib/remote/httphandler.hpp index a6a730255..ec67ae8a4 100644 --- a/lib/remote/httphandler.hpp +++ b/lib/remote/httphandler.hpp @@ -27,6 +27,7 @@ public: DECLARE_PTR_TYPEDEFS(HttpHandler); virtual bool HandleRequest( + const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, @@ -39,6 +40,7 @@ public: static void Register(const Url::Ptr& url, const HttpHandler::Ptr& handler); static void ProcessRequest( + const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/httpserverconnection.cpp b/lib/remote/httpserverconnection.cpp index cc0ecc376..17e61f160 100644 --- a/lib/remote/httpserverconnection.cpp +++ b/lib/remote/httpserverconnection.cpp @@ -35,13 +35,13 @@ using namespace icinga; auto const l_ServerHeader ("Icinga/" + Application::GetAppVersion()); -HttpServerConnection::HttpServerConnection(const String& identity, bool authenticated, const Shared::Ptr& stream) - : HttpServerConnection(identity, authenticated, stream, IoEngine::Get().GetIoContext()) +HttpServerConnection::HttpServerConnection(const WaitGroup::Ptr& waitGroup, const String& identity, bool authenticated, const Shared::Ptr& stream) + : HttpServerConnection(waitGroup, identity, authenticated, stream, IoEngine::Get().GetIoContext()) { } -HttpServerConnection::HttpServerConnection(const String& identity, bool authenticated, const Shared::Ptr& stream, boost::asio::io_context& io) - : m_Stream(stream), m_Seen(Utility::GetTime()), m_IoStrand(io), m_ShuttingDown(false), m_HasStartedStreaming(false), +HttpServerConnection::HttpServerConnection(const WaitGroup::Ptr& waitGroup, const String& identity, bool authenticated, const Shared::Ptr& stream, boost::asio::io_context& io) + : m_WaitGroup(waitGroup), m_Stream(stream), m_Seen(Utility::GetTime()), m_IoStrand(io), m_ShuttingDown(false), m_HasStartedStreaming(false), m_CheckLivenessTimer(io) { if (authenticated) { @@ -419,6 +419,7 @@ bool ProcessRequest( boost::beast::http::response& response, HttpServerConnection& server, bool& hasStartedStreaming, + const WaitGroup::Ptr& waitGroup, std::chrono::steady_clock::duration& cpuBoundWorkTime, boost::asio::yield_context& yc ) @@ -431,7 +432,7 @@ bool ProcessRequest( CpuBoundWork handlingRequest (yc); cpuBoundWorkTime = std::chrono::steady_clock::now() - start; - HttpHandler::ProcessRequest(stream, authenticatedUser, request, response, yc, server); + HttpHandler::ProcessRequest(waitGroup, stream, authenticatedUser, request, response, yc, server); } catch (const std::exception& ex) { if (hasStartedStreaming) { return false; @@ -477,7 +478,7 @@ void HttpServerConnection::ProcessMessages(boost::asio::yield_context yc) */ beast::flat_buffer buf; - for (;;) { + while (m_WaitGroup->IsLockable()) { m_Seen = Utility::GetTime(); http::parser parser; @@ -548,7 +549,7 @@ void HttpServerConnection::ProcessMessages(boost::asio::yield_context yc) m_Seen = std::numeric_limits::max(); - if (!ProcessRequest(*m_Stream, request, authenticatedUser, response, *this, m_HasStartedStreaming, cpuBoundWorkTime, yc)) { + if (!ProcessRequest(*m_Stream, request, authenticatedUser, response, *this, m_HasStartedStreaming, m_WaitGroup, cpuBoundWorkTime, yc)) { break; } diff --git a/lib/remote/httpserverconnection.hpp b/lib/remote/httpserverconnection.hpp index 63f99e19c..e4f7d257e 100644 --- a/lib/remote/httpserverconnection.hpp +++ b/lib/remote/httpserverconnection.hpp @@ -6,6 +6,7 @@ #include "remote/apiuser.hpp" #include "base/string.hpp" #include "base/tlsstream.hpp" +#include "base/wait-group.hpp" #include #include #include @@ -25,14 +26,15 @@ class HttpServerConnection final : public Object public: DECLARE_PTR_TYPEDEFS(HttpServerConnection); - HttpServerConnection(const String& identity, bool authenticated, const Shared::Ptr& stream); + HttpServerConnection(const WaitGroup::Ptr& waitGroup, const String& identity, bool authenticated, + const Shared::Ptr& stream); void Start(); void StartStreaming(); - bool Disconnected(); private: + WaitGroup::Ptr m_WaitGroup; ApiUser::Ptr m_ApiUser; Shared::Ptr m_Stream; double m_Seen; @@ -42,7 +44,8 @@ private: bool m_HasStartedStreaming; boost::asio::deadline_timer m_CheckLivenessTimer; - HttpServerConnection(const String& identity, bool authenticated, const Shared::Ptr& stream, boost::asio::io_context& io); + HttpServerConnection(const WaitGroup::Ptr& waitGroup, const String& identity, bool authenticated, + const Shared::Ptr& stream, boost::asio::io_context& io); void Disconnect(boost::asio::yield_context yc); diff --git a/lib/remote/infohandler.cpp b/lib/remote/infohandler.cpp index 6a1360b40..5fc621cd8 100644 --- a/lib/remote/infohandler.cpp +++ b/lib/remote/infohandler.cpp @@ -9,6 +9,7 @@ using namespace icinga; REGISTER_URLHANDLER("/", InfoHandler); bool InfoHandler::HandleRequest( + const WaitGroup::Ptr&, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/infohandler.hpp b/lib/remote/infohandler.hpp index e1fe98314..7396f5ac9 100644 --- a/lib/remote/infohandler.hpp +++ b/lib/remote/infohandler.hpp @@ -14,6 +14,7 @@ public: DECLARE_PTR_TYPEDEFS(InfoHandler); bool HandleRequest( + const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/mallocinfohandler.cpp b/lib/remote/mallocinfohandler.cpp index ac73e3650..f4c27cac4 100644 --- a/lib/remote/mallocinfohandler.cpp +++ b/lib/remote/mallocinfohandler.cpp @@ -18,6 +18,7 @@ using namespace icinga; REGISTER_URLHANDLER("/v1/debug/malloc_info", MallocInfoHandler); bool MallocInfoHandler::HandleRequest( + const WaitGroup::Ptr&, AsioTlsStream&, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/mallocinfohandler.hpp b/lib/remote/mallocinfohandler.hpp index 0e188f3eb..9648fac9f 100644 --- a/lib/remote/mallocinfohandler.hpp +++ b/lib/remote/mallocinfohandler.hpp @@ -13,6 +13,7 @@ public: DECLARE_PTR_TYPEDEFS(MallocInfoHandler); bool HandleRequest( + const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/modifyobjecthandler.cpp b/lib/remote/modifyobjecthandler.cpp index dabe69523..c71be6a9a 100644 --- a/lib/remote/modifyobjecthandler.cpp +++ b/lib/remote/modifyobjecthandler.cpp @@ -14,6 +14,7 @@ using namespace icinga; REGISTER_URLHANDLER("/v1/objects", ModifyObjectHandler); bool ModifyObjectHandler::HandleRequest( + const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, @@ -104,12 +105,31 @@ bool ModifyObjectHandler::HandleRequest( ArrayData results; + std::shared_lock wgLock{*waitGroup, std::try_to_lock}; + if (!wgLock) { + HttpUtility::SendJsonError(response, params, 503, "Shutting down."); + return true; + } + for (ConfigObject::Ptr obj : objs) { Dictionary::Ptr result1 = new Dictionary(); result1->Set("type", type->GetName()); result1->Set("name", obj->GetName()); + if (!waitGroup->IsLockable()) { + if (wgLock) { + wgLock.unlock(); + } + + result1->Set("code", 503); + result1->Set("status", "Action skipped: Shutting down."); + + results.emplace_back(std::move(result1)); + + continue; + } + String key; // Lock the object name of the given type to prevent from being modified/deleted concurrently. diff --git a/lib/remote/modifyobjecthandler.hpp b/lib/remote/modifyobjecthandler.hpp index f4693013f..f299acd6e 100644 --- a/lib/remote/modifyobjecthandler.hpp +++ b/lib/remote/modifyobjecthandler.hpp @@ -14,6 +14,7 @@ public: DECLARE_PTR_TYPEDEFS(ModifyObjectHandler); bool HandleRequest( + const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/objectqueryhandler.cpp b/lib/remote/objectqueryhandler.cpp index fbd5c7e70..f6f049e4e 100644 --- a/lib/remote/objectqueryhandler.cpp +++ b/lib/remote/objectqueryhandler.cpp @@ -89,6 +89,7 @@ Dictionary::Ptr ObjectQueryHandler::SerializeObjectAttrs(const Object::Ptr& obje } bool ObjectQueryHandler::HandleRequest( + const WaitGroup::Ptr&, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/objectqueryhandler.hpp b/lib/remote/objectqueryhandler.hpp index 691b2cfcf..376eb661e 100644 --- a/lib/remote/objectqueryhandler.hpp +++ b/lib/remote/objectqueryhandler.hpp @@ -14,6 +14,7 @@ public: DECLARE_PTR_TYPEDEFS(ObjectQueryHandler); bool HandleRequest( + const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/statushandler.cpp b/lib/remote/statushandler.cpp index 310ce0d87..bf14152f8 100644 --- a/lib/remote/statushandler.cpp +++ b/lib/remote/statushandler.cpp @@ -69,6 +69,7 @@ public: }; bool StatusHandler::HandleRequest( + const WaitGroup::Ptr&, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/statushandler.hpp b/lib/remote/statushandler.hpp index c722ab3e2..109fd4881 100644 --- a/lib/remote/statushandler.hpp +++ b/lib/remote/statushandler.hpp @@ -14,6 +14,7 @@ public: DECLARE_PTR_TYPEDEFS(StatusHandler); bool HandleRequest( + const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/templatequeryhandler.cpp b/lib/remote/templatequeryhandler.cpp index e70dafb65..a68ad6dad 100644 --- a/lib/remote/templatequeryhandler.cpp +++ b/lib/remote/templatequeryhandler.cpp @@ -76,6 +76,7 @@ public: }; bool TemplateQueryHandler::HandleRequest( + const WaitGroup::Ptr&, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/templatequeryhandler.hpp b/lib/remote/templatequeryhandler.hpp index 503bc8560..312cf4221 100644 --- a/lib/remote/templatequeryhandler.hpp +++ b/lib/remote/templatequeryhandler.hpp @@ -14,6 +14,7 @@ public: DECLARE_PTR_TYPEDEFS(TemplateQueryHandler); bool HandleRequest( + const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/typequeryhandler.cpp b/lib/remote/typequeryhandler.cpp index b30dbb14a..b2184344d 100644 --- a/lib/remote/typequeryhandler.cpp +++ b/lib/remote/typequeryhandler.cpp @@ -47,6 +47,7 @@ public: }; bool TypeQueryHandler::HandleRequest( + const WaitGroup::Ptr&, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/typequeryhandler.hpp b/lib/remote/typequeryhandler.hpp index 5489cb232..45cbc38ec 100644 --- a/lib/remote/typequeryhandler.hpp +++ b/lib/remote/typequeryhandler.hpp @@ -14,6 +14,7 @@ public: DECLARE_PTR_TYPEDEFS(TypeQueryHandler); bool HandleRequest( + const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/variablequeryhandler.cpp b/lib/remote/variablequeryhandler.cpp index 7264338cb..40552dd7d 100644 --- a/lib/remote/variablequeryhandler.cpp +++ b/lib/remote/variablequeryhandler.cpp @@ -57,6 +57,7 @@ public: }; bool VariableQueryHandler::HandleRequest( + const WaitGroup::Ptr&, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, diff --git a/lib/remote/variablequeryhandler.hpp b/lib/remote/variablequeryhandler.hpp index 48e73be35..d145f5b59 100644 --- a/lib/remote/variablequeryhandler.hpp +++ b/lib/remote/variablequeryhandler.hpp @@ -14,6 +14,7 @@ public: DECLARE_PTR_TYPEDEFS(VariableQueryHandler); bool HandleRequest( + const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, const ApiUser::Ptr& user, boost::beast::http::request& request, From 15a8114b4b76f878a1ea433977645d6a571afba2 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Mon, 16 Jun 2025 09:08:19 +0200 Subject: [PATCH 356/415] icinga-installer: statically link MSVC runtime library on CMake 3.15+ CMake 3.15 introduced the `MSVC_RUNTIME_LIBRARY` property as a way to specify which MSVC runtime library is used and how it is linked. Also with that release, policy CMP0091 was introduced where the new behavior no longer adds the corresponding compile flags to the `CMAKE__FLAGS_` variables. The new policy was enabled by 7f164bda96341272be385fa1359a26f97eb9d2b4, resulting in the MSI no longer working on previous Windows Server versions (2019 for example) as the CMake configuration replaced those compile flags (lines 3-9 in the same file) which no longer works when they are no longer part of the string. This commit fixes this regression by setting the `MSVC_RUNTIME_LIBRARY` property on the icinga-installer target. I've also added a comment stating my understanding of why this is necessary. Unfortunately, I couldn't find an explanation of why replacing the /MD with the /MT flag was done originally in the project history, so it's a bit of guesswork. https://cmake.org/cmake/help/latest/policy/CMP0091.html https://cmake.org/cmake/help/latest/prop_tgt/MSVC_RUNTIME_LIBRARY.html --- icinga-installer/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/icinga-installer/CMakeLists.txt b/icinga-installer/CMakeLists.txt index 6ac5e1f04..1cd80b513 100644 --- a/icinga-installer/CMakeLists.txt +++ b/icinga-installer/CMakeLists.txt @@ -19,6 +19,10 @@ set_target_properties( FOLDER Bin OUTPUT_NAME icinga2-installer LINK_FLAGS "/SUBSYSTEM:WINDOWS" + + # Use a statically-linked runtime library as this binary is run during the installation process where the other DLLs + # may not have been installed already and the system-provided version may be too old. + MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>" ) target_link_libraries(icinga-installer shlwapi) From 3b729e9cd70503fcfa7c925e2e57f78d8eebdcf3 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Mon, 16 Jun 2025 13:02:33 +0200 Subject: [PATCH 357/415] GHA: Windows: upgrade to VS 2022 --- .github/workflows/windows.yml | 2 +- doc/win-dev.ps1 | 6 +++--- tools/win32/configure-dev.ps1 | 6 +++--- tools/win32/configure.ps1 | 4 ++-- tools/win32/load-vsenv.ps1 | 2 +- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index db4806915..052254897 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -21,7 +21,7 @@ jobs: matrix: bits: [32, 64] - runs-on: windows-2019 + runs-on: windows-2025 env: BITS: '${{ matrix.bits }}' diff --git a/doc/win-dev.ps1 b/doc/win-dev.ps1 index 46fc1213a..09ba714b9 100644 --- a/doc/win-dev.ps1 +++ b/doc/win-dev.ps1 @@ -11,8 +11,8 @@ function ThrowOnNativeFailure { } -$VsVersion = 2019 -$MsvcVersion = '14.2' +$VsVersion = 2022 +$MsvcVersion = '14.3' $BoostVersion = @(1, 88, 0) $OpensslVersion = '3_0_16' @@ -74,7 +74,6 @@ try { if (-not $Env:GITHUB_ACTIONS) { choco install -y ` "visualstudio${VsVersion}community" ` - "visualstudio${VsVersion}-workload-netcoretools" ` "visualstudio${VsVersion}-workload-vctools" ` "visualstudio${VsVersion}-workload-manageddesktop" ` "visualstudio${VsVersion}-workload-nativedesktop" ` @@ -83,6 +82,7 @@ if (-not $Env:GITHUB_ACTIONS) { git ` cmake ` winflexbison3 ` + netfx-4.6-devpack ` windows-sdk-8.1 ` wixtoolset ThrowOnNativeFailure diff --git a/tools/win32/configure-dev.ps1 b/tools/win32/configure-dev.ps1 index a82560d25..311804fa3 100644 --- a/tools/win32/configure-dev.ps1 +++ b/tools/win32/configure-dev.ps1 @@ -1,6 +1,6 @@ Set-PsDebug -Trace 1 -# Specify default targets for VS 2019 for developers. +# Specify default targets for VS 2022 for developers. if (-not (Test-Path env:ICINGA2_BUILDPATH)) { $env:ICINGA2_BUILDPATH = '.\debug' @@ -22,7 +22,7 @@ if (-not ($env:PATH -contains $env:CMAKE_PATH)) { $env:PATH = $env:CMAKE_PATH + ';' + $env:PATH } if (-not (Test-Path env:CMAKE_GENERATOR)) { - $env:CMAKE_GENERATOR = 'Visual Studio 16 2019' + $env:CMAKE_GENERATOR = 'Visual Studio 17 2022' } if (-not (Test-Path env:CMAKE_GENERATOR_PLATFORM)) { $env:CMAKE_GENERATOR_PLATFORM = 'x64' @@ -37,7 +37,7 @@ if (-not (Test-Path env:BOOST_ROOT)) { $env:BOOST_ROOT = 'c:\local\boost_1_88_0' } if (-not (Test-Path env:BOOST_LIBRARYDIR)) { - $env:BOOST_LIBRARYDIR = 'c:\local\boost_1_88_0\lib64-msvc-14.2' + $env:BOOST_LIBRARYDIR = 'c:\local\boost_1_88_0\lib64-msvc-14.3' } if (-not (Test-Path env:FLEX_BINARY)) { $env:FLEX_BINARY = 'C:\ProgramData\chocolatey\bin\win_flex.exe' diff --git a/tools/win32/configure.ps1 b/tools/win32/configure.ps1 index db961a2e5..1cc8dfb3d 100644 --- a/tools/win32/configure.ps1 +++ b/tools/win32/configure.ps1 @@ -17,7 +17,7 @@ if (-not ($env:PATH -contains $env:CMAKE_PATH)) { $env:PATH = $env:CMAKE_PATH + ';' + $env:PATH } if (-not (Test-Path env:CMAKE_GENERATOR)) { - $env:CMAKE_GENERATOR = 'Visual Studio 16 2019' + $env:CMAKE_GENERATOR = 'Visual Studio 17 2022' } if (-not (Test-Path env:BITS)) { $env:BITS = 64 @@ -39,7 +39,7 @@ if (-not (Test-Path env:BOOST_ROOT)) { $env:BOOST_ROOT = "c:\local\boost_1_88_0-Win${env:BITS}" } if (-not (Test-Path env:BOOST_LIBRARYDIR)) { - $env:BOOST_LIBRARYDIR = "c:\local\boost_1_88_0-Win${env:BITS}\lib${env:BITS}-msvc-14.2" + $env:BOOST_LIBRARYDIR = "c:\local\boost_1_88_0-Win${env:BITS}\lib${env:BITS}-msvc-14.3" } if (-not (Test-Path env:FLEX_BINARY)) { $env:FLEX_BINARY = 'C:\ProgramData\chocolatey\bin\win_flex.exe' diff --git a/tools/win32/load-vsenv.ps1 b/tools/win32/load-vsenv.ps1 index c5323dc2a..a876c6148 100644 --- a/tools/win32/load-vsenv.ps1 +++ b/tools/win32/load-vsenv.ps1 @@ -18,7 +18,7 @@ if (-not (Test-Path $BUILD)) { if (Test-Path env:VS_INSTALL_PATH) { $VSBASE = $env:VS_INSTALL_PATH } else { - $VSBASE = "C:\Program Files (x86)\Microsoft Visual Studio\2019" + $VSBASE = "C:\Program Files\Microsoft Visual Studio\2022" } if (Test-Path env:BITS) { From 881e1cc9cbb9cbe34995bd0d08c31eb95c37a400 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Tue, 17 Jun 2025 09:03:51 +0200 Subject: [PATCH 358/415] docs: Fix Fedora Repository, Icinga DB, SELinux With Fedora 41, DNF was upgraded to version 5, breaking the command line API of "dnf config-manager"[^0]. Unfortunately, DNF 5's addrepo does not work with a simple URL anymore, but requires to construct a .repo file. Furthermore, no information about trusting the Icinga signing key was available, resulting in one being unable to install packages. This was already the case for Fedora 40, still using DNF 4. Since we are building Icinga DB for Fedora, I have included Icinga DB documentation for Fedora. Otherwise, this section was empty. Finally, the icingadb-redis-selinux package was mentioned for distributions were we started to build SELinux packages for[^1]. [^0]: https://docs.fedoraproject.org/en-US/quick-docs/adding-or-removing-software-repositories-in-fedora/#_adding_repositories [^1]: https://github.com/Icinga/icingadb/issues/580 --- doc/02-installation.md | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/doc/02-installation.md b/doc/02-installation.md index bff6529eb..5c1091fa3 100644 --- a/doc/02-installation.md +++ b/doc/02-installation.md @@ -106,9 +106,10 @@ dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-${OSVER}.n ### Fedora Repository ```bash -dnf install -y 'dnf-command(config-manager)' -dnf config-manager --add-repo https://packages.icinga.com/fedora/$(. /etc/os-release; echo "$VERSION_ID")/release +rpm --import https://packages.icinga.com/icinga.key +curl -o /etc/yum.repos.d/ICINGA-release.repo https://packages.icinga.com/fedora/ICINGA-release.repo ``` + @@ -368,7 +369,7 @@ Restart Icinga 2 for these changes to take effect. systemctl restart icinga2 ``` - + ## Set up Icinga DB Icinga DB is a set of components for publishing, synchronizing and @@ -409,7 +410,13 @@ A Redis server from version 6.2 is required. #### Install Icinga DB Redis Package -Use your distribution's package manager to install the `icingadb-redis` package as follows: +Use your distribution's package manager to install the `icingadb-redis` package. + + +!!! tip + + If you have [SELinux](22-selinux.md) enabled, the package `icingadb-redis-selinux` is also required. + From bf628960f385440e8f95e0efd5c249baca68e9fd Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Tue, 17 Jun 2025 11:15:16 +0200 Subject: [PATCH 359/415] .mailmap: Merge Alvar email addresses Merging PRs via GitHub resulted in using the noreply email addresses. By adding a .mailmap entry, they are hidden from the git log. Before: > $ git shortlog -sne v2.14.0..HEAD | grep -i alvar > 22 Alvar Penning > 1 Alvar <8402811+oxzi@users.noreply.github.com> > 1 alvar <8402811+oxzi@users.noreply.github.com> After: > $ git shortlog -sne v2.14.0..HEAD | grep -i alvar > 24 Alvar Penning --- .mailmap | 1 + 1 file changed, 1 insertion(+) diff --git a/.mailmap b/.mailmap index 36496bc29..5026498e6 100644 --- a/.mailmap +++ b/.mailmap @@ -35,6 +35,7 @@ Alexander A. Klimov Alex +Alvar Penning <8402811+oxzi@users.noreply.github.com> Baptiste Beauplat Carsten Köbke Carsten Koebke Claudio Kuenzler From 1fc8395debb3d7c46429c5d7e5d296433c73972f Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 11 Jun 2025 12:04:00 +0200 Subject: [PATCH 360/415] Release v2.15.0 --- CHANGELOG.md | 153 ++++++++++++++++++++++++++++++++++++++++++++++++ ICINGA2_VERSION | 2 +- 2 files changed, 154 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94c553cf5..1ea49615d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,159 @@ documentation before upgrading to a new release. Released closed milestones can be found on [GitHub](https://github.com/Icinga/icinga2/milestones?state=closed). +## 2.15.0 (2025-06-18) + +This Icinga 2 release is focused on adding Icinga 2 dependencies support to Icinga DB, but also includes a number +of bugfixes, enhancements and code quality improvements. Below is a summary of the most important changes, for the +complete list of issues and PRs, please see the [milestone on GitHub](https://github.com/Icinga/icinga2/issues?q=is%3Aclosed+milestone%3A2.15.0). + +### Notes + +Thanks to all contributors: +[ChrLau](https://github.com/Icinga/icinga2/pulls?q=is%3Apr+is%3Aclosed+milestone%3A2.15.0+author%3AChrLau), +[Josef-Friedrich](https://github.com/Icinga/icinga2/pulls?q=is%3Apr+is%3Aclosed+milestone%3A2.15.0+author%3AJosef-Friedrich), +[LordHepipud](https://github.com/Icinga/icinga2/pulls?q=is%3Apr+is%3Aclosed+milestone%3A2.15.0+author%3ALordHepipud), +[OdyX](https://github.com/Icinga/icinga2/pulls?q=is%3Apr+is%3Aclosed+milestone%3A2.15.0+author%3AOdyX), +[RincewindsHat](https://github.com/Icinga/icinga2/pulls?q=is%3Apr+is%3Aclosed+milestone%3A2.15.0+author%3ARincewindsHat), +[SebastianOpeni](https://github.com/Icinga/icinga2/pulls?q=is%3Apr+is%3Aclosed+milestone%3A2.15.0+author%3ASebastianOpeni), +[SpeedD3](https://github.com/Icinga/icinga2/pulls?q=is%3Apr+is%3Aclosed+milestone%3A2.15.0+author%3ASpeedD3), +[Tqnsls](https://github.com/Icinga/icinga2/pulls?q=is%3Apr+is%3Aclosed+milestone%3A2.15.0+author%3ATqnsls), +[botovq](https://github.com/Icinga/icinga2/pulls?q=is%3Apr+is%3Aclosed+milestone%3A2.15.0+author%3Abotovq), +[cycloon](https://github.com/Icinga/icinga2/pulls?q=is%3Apr+is%3Aclosed+milestone%3A2.15.0+author%3Acycloon), +[legioner0](https://github.com/Icinga/icinga2/pulls?q=is%3Apr+is%3Aclosed+milestone%3A2.15.0+author%3Alegioner0), +[legna-namor](https://github.com/Icinga/icinga2/pulls?q=is%3Apr+is%3Aclosed+milestone%3A2.15.0+author%3Alegna-namor), +[macdems](https://github.com/Icinga/icinga2/pulls?q=is%3Apr+is%3Aclosed+milestone%3A2.15.0+author%3Amacdems), +[mathiasaerts](https://github.com/Icinga/icinga2/pulls?q=is%3Apr+is%3Aclosed+milestone%3A2.15.0+author%3Amathiasaerts), +[mcodato](https://github.com/Icinga/icinga2/pulls?q=is%3Apr+is%3Aclosed+milestone%3A2.15.0+author%3Amcodato), +[n-rodriguez](https://github.com/Icinga/icinga2/pulls?q=is%3Apr+is%3Aclosed+milestone%3A2.15.0+author%3An-rodriguez), +[netphantm](https://github.com/Icinga/icinga2/pulls?q=is%3Apr+is%3Aclosed+milestone%3A2.15.0+author%3Anetphantm), +[nicolasberens](https://github.com/Icinga/icinga2/pulls?q=is%3Apr+is%3Aclosed+milestone%3A2.15.0+author%3Anicolasberens), +[oldelvet](https://github.com/Icinga/icinga2/pulls?q=is%3Apr+is%3Aclosed+milestone%3A2.15.0+author%3Aoldelvet), +[peteeckel](https://github.com/Icinga/icinga2/pulls?q=is%3Apr+is%3Aclosed+milestone%3A2.15.0+author%3Apeteeckel), +[tbauriedel](https://github.com/Icinga/icinga2/pulls?q=is%3Apr+is%3Aclosed+milestone%3A2.15.0+author%3Atbauriedel), +[w1ll-i-code](https://github.com/Icinga/icinga2/pulls?q=is%3Apr+is%3Aclosed+milestone%3A2.15.0+author%3Aw1ll-i-code), +[ymartin-ovh](https://github.com/Icinga/icinga2/pulls?q=is%3Apr+is%3Aclosed+milestone%3A2.15.0+author%3Aymartin-ovh) + +### Breaking Changes + +* API: Fix `/v1/objects/*` queries with `attrs` set to `[]` to return empty attributes instead of all of them. #8169 +* Drop the undocumented `Checkable#process_check_result` and broken `System#track_parents` DSL functions. #10457 + +### Enhancements + +* Gracefully disconnect all clients on shutdown and prevent from accepting new connections. #10460 +* Icinga DB: Send data to Redis® exactly as they're stored in the database to avoid extra value-mapping routines by the Go daemon. #10452 +* Add support for Icinga 2 dependencies in Icinga DB. #10290 +* Take host/service reachability into account when computing its severity. #10399 +* Rework the dependency cycle detection to efficiently handle large configs and provide better error messages. #10360 +* Don't log next check timestamp in scientific notation. #10352 +* Automatically remove child downtimes when removing parent downtime. #10345 +* Ensure compatibility with Boost version up to v1.88. #10278 #10419 +* Reject infinite performance data values. #10077 +* Support `host_template` and `service_template` tags in `ElasticsearchWriter`. #10074 +* Icinga DB: Support Redis® username authentication. #10102 +* Cluster: Distribute host child objects (e.g. services, notifications, etc.) based on the host's name. #10161 +* Icinga DB Check: Report an error if both Icinga DB instances are responsible in a HA setup. #10188 +* Windows: upgrade build toolchain to Visual Studio 2022. #9747 + +### Bugfixes + +* Core + * Use `Checkable#check_timeout` also for rescheduling remote checks. #10443 + * Log: Don't unnecessarily buffer log messages that are going to be dropped anyway. #10177 + * Don't loose perfdata counter (`c`) unit when normalizing performance data for Icinga DB. #10432 + * Fix broken SELinux policy on Fedora ≥ 41 due to the new `/usr/sbin` to `/usr/bin` equivalence. #10429 + * Don't load `Notification` objects before `User` and `UserGroup` objects to allow them to be referenced in notifications. #10427 + * Ensure consistent DST handling across different platforms. #10422 + * Fix Icinga 2 doesn't generate a core dump when it crashes with SIGABRT. #10416 + * Don't process concurrent checks for the same checkable. #10372 + * Don't process check results after the checker and API listener have been stopped. #10397 + * Avoid zombie processes on plugin execution timeout on busy systems. #10375 + * Properly restore the notification object state on `Recovery` notification. #10361 + * Fix incorrectly dropped acknowledgement and recovery notifications. #10211 + * Prevent checks from always being rescheduled outside the configured `check_period`. #10070 + * Don't send reminder notifications after a `Custom` notification while `interval` is set to `0`. #7818 + * Reset all signal handlers of child processes to their defaults before starting a plugin. #8011 + * tests: Fix `FormatDateTime` test cases with invalid formats on macOS and all BSD-based systems. #10149 + * Mark move constructor and assignment operator in `String` as `noexcept` to allow optimizations. #10353 #10365 +* Cluster and API + * Fix an inverted condition in `ApiListener#IsHACluster()` that caused to always return `true` in a non-HA setup. #10417 + * Don't silently accept authenticated JSON-RPC connections with no valid endpoint. #10415 + * Sync `Notification#notified_problem_users` across the cluster to prevent lost recovery notifications. #10380 + * Remove superfluous `)` from a HTTP request log message. #9966 + * Disable TLS renegotiation (handshake on existing connection) on OpenBSD as well. #9943 + * Log also the underlying error message when a HTTP request is closed with `No data received` by Icinga 2. #9928 + * Fix a deadlock triggered by concurrent `/v1/actions/add-comment` and `/v1/actions/acknowledge-problem` requests on + the same checkable, as well as a crash that might occur when running perfectly timed `/v1/actions/add-comment` + and `/v1/actions/remove-comment` requests targeting the same comment. #9924 +* Icinga DB + * Fix missing acknowledgement and flapping history entries due to a number overflow. #10467 + * Send downtime `cancel_time` only if it is cancelled. #10379 + * Send only the necessary data to the `icinga:stats` Redis® stream. #10359 + * Remove a spin lock in `RedisConnection#Connect()` to avoid busy waiting. #10265 +* Writers + * Serialize all required metrics before queueing them to a `WorkQueue`. #10420 + * `OpenTsdbWriter`: Include checkable name in log messages to ease troubleshooting. #10009 + * `OpenTsdbWriter`: Don't send custom empty tags. #7928 + * `InfluxDBWriter`: Add missing closing quote in validation error message. #10174 + +### ITL + +* Add `--maintenance_mode_state` (`$vmware_maintenance_mode_state`) argument to `vmware-esx-command` check command. #10435 +* Add `-n` (`$load_procs_to_show$`) argument to `load` check command. #10426 +* Add `--inode-perfdata` (`$disk_np_inode_perfdata$`) argument to `disk` check command. #10395 +* Add `-r` (`$ssh_remote_version$`) and `-P` (`$ssh_remote_protocol$`) arguments to `ssh` check command. #10283 +* Add `--unplugged_nics_state` (`$vmware_unplugged_nics_state$`) argument to `vmware-esx-soap-host-net` and `vmware-esx-soap-host-net-nic` check commands. #10261 +* Add `-X` (`$proc_exclude_process$`) argument to `procs` check command. #10232 +* Add `--dane` (`$ssl_cert_dane$`) argument to `ssl_cert` check command. #10196 +* Fix `check_ssl_cert` deprecation warnings. #9758 +* Fix `check_systemd` executable name add add all missing arguments. #10035 +* Add `-M` (`$snmp_multiplier$` & `$snmpv3_multiplier$`) argument to `snmp` and `snmpv3` check commands. #9975 +* Add `--continue-after-certificate` (`$http_certificate_continue$`) argument to `http` check command. #9974 +* Add `--ignore-maximum-validity` (`$ssl_cert_ignore_maximum_validity$`) argument to `ssl_cert` check command. #10396 +* Add `--maximum-validity` (`$ssl_cert_maximum_validity$`) argument to `ssl_cert` check command. #9881 +* Add `--url` (`$ssl_cert_http_url$`) argument to `ssl_cert` check command. #9759 +* Add `fuse.sshfs` and `fuse.*` (supported only by Monitoring Plugins) to the list of default disk exclude types. #9749 +* Add `check_curl` check command. #9205 +* Add the `--extra-opts` argument to various commands that support it. #8010 + +### Documentation + +* Don't use `dnf config-manager` to configure Fedora repository and mention `icingadb-redis-selinux` package. #10479 +* Update the outdated cold startup duration documentation to reflect the current behavior. #10446 +* Indent second-level unordered lists with four spaces to correctly render them in the HTML documentation. #10441 +* Add a reference to the check result state documentation from within the Advanced Topics section. #10421 +* Improve the documentation of how to generate Icinga 2 core dumps. #10418 +* Update Icinga 2 CLI output examples to match the current output. #10323 +* Fix incorrect `ping_timeout` value in the `hostalive` check command documentation. #10069 + +### Code Quality + +* Simplify deferred SSL shutdown in `ApiListener#NewClientHandlerInternal()`. #10301 +* Don't unnecessarily shuffle configuration items during config load. #10008 +* Sort config types by their load dependencies at namespace initialization time to save some round trips during config load. #10148 +* Fix `livestatus` build error on macOS without unity builds. #10176 +* Remove unused methods in `SharedObject` class. #10456 +* Remove unused `ProcessingResult#NoCheckResult` enum value. #10444 +* CMake: Drop all third-party cmake modules and use the ones shipped with CMake v3.8+. #10403 +* CMake: Raise the minimum required policy to `3.8`. #10402 #10478 +* CMake: Turn on `-Wsuggest-override` to warn about missing `override` specifiers. #10225 #10356 +* Make `icinga::Empty` a constant to prevent accidental modifications. #10224 +* Remove various unused methods in the `Registry` class. #10222 +* Fix missing parent `std::atomic` constructor call in our `Atomic` wrapper class. #10215 +* Drop unused `m_NextHeartbeat` member variable from `JsonRpcConnection`. #10208 +* Enhance some of the validation error messages. #10201 +* Don't allow `Type#GetLoadDependencies()` to return non-config object type dependencies. #10169 +* Don't allow `Type#GetLoadDependencies()` to return a set of nullptr type dependencies. #10155 +* Remove EOL distros detection code from `Utility::ReleaseHelper()` function. #10147 +* Remove dead code in TLS `GetSignatureAlgorithm()` function. #9882 +* Mark `Logger#GetSeverity()` as non-virtual to avoid unnecessary vtable lookups. #9851 +* Remove unused `Stream#Peak()` method and unused `allow_partial` parameter from `Stream#Read()`. #9734 #9736 +* Suppress compiler warnings in third-party libraries. #9732 +* Fix various compiler warnings. #9731 #10442 +* Reduce task function allocation overhead by using a per-thread created lambda in `WorkQueue`. #9575 +* Remove redundant trailing empty lines and add missing newlines in some files. #7799 + ## 2.14.6 (2025-05-27) This security release fixes a critical issue in the certificate renewal logic in Icinga 2, which diff --git a/ICINGA2_VERSION b/ICINGA2_VERSION index fa973b5e9..66639bc0c 100644 --- a/ICINGA2_VERSION +++ b/ICINGA2_VERSION @@ -1,2 +1,2 @@ -Version: 2.14.0 +Version: 2.15.0 Revision: 1 From 2b8fbe5d92779dc60ad6fe477f7e21753e3e1c2a Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Wed, 18 Jun 2025 11:29:51 +0200 Subject: [PATCH 361/415] Add a hint for upgrading docs to the release checklist --- .github/ISSUE_TEMPLATE/release.md | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/release.md b/.github/ISSUE_TEMPLATE/release.md index def3b2ef3..2d67d00bd 100644 --- a/.github/ISSUE_TEMPLATE/release.md +++ b/.github/ISSUE_TEMPLATE/release.md @@ -13,6 +13,7 @@ assignees: '' - [ ] Update bundled Windows dependencies - [ ] Harden global TLS defaults (consult https://ssl-config.mozilla.org) - [ ] Update `CHANGELOG.md` +- [ ] Update `doc/16-upgrading-icinga-2.md` if applicable - [ ] Create and push a signed tag for the version - [ ] Build and release DEB and RPM packages - [ ] Build and release Windows packages From e105eefee71afbb5ea25f4c17e7cc1c356e57e9d Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Wed, 18 Jun 2025 11:50:10 +0200 Subject: [PATCH 362/415] Add upgrading docs for v2.15.0 --- doc/16-upgrading-icinga-2.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/doc/16-upgrading-icinga-2.md b/doc/16-upgrading-icinga-2.md index 698fb8785..2c5a1d0a5 100644 --- a/doc/16-upgrading-icinga-2.md +++ b/doc/16-upgrading-icinga-2.md @@ -8,6 +8,28 @@ Specific version upgrades are described below. Please note that version updates are incremental. An upgrade from v2.6 to v2.8 requires to follow the instructions for v2.7 too. +## Upgrading to v2.15 + +### Icinga DB + +Version 2.15.0 of Icinga 2 is released alongside Icinga DB 1.4.0 and Icinga DB +Web 1.2.0. A change to the internal communication API requires these updates to +be applied together. To put it simply, Icinga 2.15.0 needs Icinga DB 1.4.0 or +later. + +### REST API Attribute Filter + +When [querying objects](12-icinga2-api.md#icinga2-api-config-objects-query) +using the API, specifying `{"attrs":[]}` now returns the objects with no +attributes. Not supplying the parameter or using `{"attrs":null}` still returns +the unfiltered list of all attributes. + +### Removed DSL Functions + +The undocumented `Checkable#process_check_result` and `System#track_parents` +functions were removed from the Icinga 2 config language (the +`process-check-result` API action is unaffected by this). + ## Upgrading to v2.14 ### Dependencies and Redundancy Groups From ec48dae331e19d1bfe0c98d8433054325f0af3cd Mon Sep 17 00:00:00 2001 From: Chris Malton Date: Wed, 18 Jun 2025 16:55:31 +0100 Subject: [PATCH 363/415] Correct a problem with expiry times not being passed through to AcknowledgeProblem --- lib/icinga/externalcommandprocessor.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/icinga/externalcommandprocessor.cpp b/lib/icinga/externalcommandprocessor.cpp index fbc0432a4..33831a491 100644 --- a/lib/icinga/externalcommandprocessor.cpp +++ b/lib/icinga/externalcommandprocessor.cpp @@ -657,7 +657,7 @@ void ExternalCommandProcessor::AcknowledgeSvcProblemExpire(double, const std::ve << "Setting timed acknowledgement for service '" << service->GetName() << "'" << (notify ? "" : ". Disabled notification"); Comment::AddComment(service, CommentAcknowledgement, arguments[6], arguments[7], persistent, timestamp, sticky); - service->AcknowledgeProblem(arguments[6], arguments[7], sticky ? AcknowledgementSticky : AcknowledgementNormal, notify, persistent, timestamp); + service->AcknowledgeProblem(arguments[6], arguments[7], sticky ? AcknowledgementSticky : AcknowledgementNormal, notify, persistent, Utility::GetTime(), timestamp); } void ExternalCommandProcessor::RemoveSvcAcknowledgement(double, const std::vector& arguments) @@ -731,7 +731,7 @@ void ExternalCommandProcessor::AcknowledgeHostProblemExpire(double, const std::v } Comment::AddComment(host, CommentAcknowledgement, arguments[5], arguments[6], persistent, timestamp, sticky); - host->AcknowledgeProblem(arguments[5], arguments[6], sticky ? AcknowledgementSticky : AcknowledgementNormal, notify, persistent, timestamp); + host->AcknowledgeProblem(arguments[5], arguments[6], sticky ? AcknowledgementSticky : AcknowledgementNormal, notify, persistent, Utility::GetTime(), timestamp); } void ExternalCommandProcessor::RemoveHostAcknowledgement(double, const std::vector& arguments) From 035f13090170c25227d5cdf2190da1dc90afa49b Mon Sep 17 00:00:00 2001 From: Chris Malton Date: Mon, 23 Jun 2025 09:17:45 +0100 Subject: [PATCH 364/415] Update AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 6b648735c..484c8dd1d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -48,6 +48,7 @@ C C Magnus Gustavsson Carlos Cesario Carsten Köbke Chris Boot +Chris Malton Christian Birk Christian Gut Christian Harke From 730a51ccb0f82c6bbd2a5e6e15e5c6d473341407 Mon Sep 17 00:00:00 2001 From: Markus Opolka Date: Mon, 30 Jun 2025 16:21:31 +0200 Subject: [PATCH 365/415] docs: Mention Elasticsearch prefix for indices --- doc/09-object-types.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/09-object-types.md b/doc/09-object-types.md index 67735e986..5c802a946 100644 --- a/doc/09-object-types.md +++ b/doc/09-object-types.md @@ -1211,7 +1211,7 @@ Configuration Attributes: --------------------------|-----------------------|---------------------------------- host | String | **Required.** Elasticsearch host address. Defaults to `127.0.0.1`. port | Number | **Required.** Elasticsearch port. Defaults to `9200`. - index | String | **Required.** Elasticsearch index name. Defaults to `icinga2`. + index | String | **Required.** Prefix for the index names. Defaults to `icinga2`. enable\_send\_perfdata | Boolean | **Optional.** Send parsed performance data metrics for check results. Defaults to `false`. flush\_interval | Duration | **Optional.** How long to buffer data points before transferring to Elasticsearch. Defaults to `10s`. flush\_threshold | Number | **Optional.** How many data points to buffer before forcing a transfer to Elasticsearch. Defaults to `1024`. From 3a031a1f55b6f29a5d6bb3db81134b94a2701f54 Mon Sep 17 00:00:00 2001 From: Markus Opolka Date: Mon, 30 Jun 2025 16:50:34 +0200 Subject: [PATCH 366/415] docs: Fix various misspellings --- doc/03-monitoring-basics.md | 12 ++++++------ doc/05-service-monitoring.md | 4 ++-- doc/06-distributed-monitoring.md | 2 +- doc/09-object-types.md | 2 +- doc/10-icinga-template-library.md | 8 ++++---- doc/12-icinga2-api.md | 6 +++--- doc/14-features.md | 6 +++--- doc/15-troubleshooting.md | 2 +- doc/16-upgrading-icinga-2.md | 4 ++-- doc/17-language-reference.md | 2 +- doc/22-selinux.md | 10 +++++----- 11 files changed, 29 insertions(+), 29 deletions(-) diff --git a/doc/03-monitoring-basics.md b/doc/03-monitoring-basics.md index 92cfc8854..5e2710f9f 100644 --- a/doc/03-monitoring-basics.md +++ b/doc/03-monitoring-basics.md @@ -766,7 +766,7 @@ apply Notification "mail-icingaadmin" to Host { A more advanced example is to use [apply rules with for loops on arrays or dictionaries](03-monitoring-basics.md#using-apply-for) provided by -[custom atttributes](03-monitoring-basics.md#custom-variables) or groups. +[custom attributes](03-monitoring-basics.md#custom-variables) or groups. Remember the examples shown for [custom variable values](03-monitoring-basics.md#custom-variables-values): @@ -3172,16 +3172,16 @@ i.e. to consider the parent unreachable only if no dependency is fulfilled. Think of a host connected to both a network and a storage switch vs. a host connected to redundant routers. Sometimes you even want a mixture of both. -Think of a service like SSH depeding on both LDAP and DNS to function, +Think of a service like SSH depending on both LDAP and DNS to function, while operating redundant LDAP servers as well as redundant DNS resolvers. -Before v2.12, Icinga regarded all dependecies as cumulative. +Before v2.12, Icinga regarded all dependencies as cumulative. In v2.12 and v2.13, Icinga regarded all dependencies redundant. -The latter led to unrelated services being inadvertantly regarded to be redundant to each other. +The latter led to unrelated services being inadvertently regarded to be redundant to each other. v2.14 restored the former behavior and allowed to override it. -I.e. all dependecies are regarded as essential for the parent by default. -Specifying the `redundancy_group` attribute for two dependecies of a child object with the equal value +I.e. all dependencies are regarded as essential for the parent by default. +Specifying the `redundancy_group` attribute for two dependencies of a child object with the equal value causes them to be regarded as redundant (only inside that redundancy group). diff --git a/doc/05-service-monitoring.md b/doc/05-service-monitoring.md index c46139d94..130cc4e19 100644 --- a/doc/05-service-monitoring.md +++ b/doc/05-service-monitoring.md @@ -326,7 +326,7 @@ object CheckCommand "systemd" { // Plugin name without 'check_' prefix Run a config validation to see if that works, `icinga2 daemon -C` Next, analyse the plugin parameters. Plugins with a good help output show -optional parameters in square brackes. This is the case for all parameters +optional parameters in square brackets. This is the case for all parameters for this plugin. If there are required parameters, use the `required` key inside the argument. @@ -689,7 +689,7 @@ liters (l) | ml, l, hl The UoM "c" represents a continuous counter (e.g. interface traffic counters). -Unknown UoMs are discarted (as if none was given). +Unknown UoMs are discarded (as if none was given). A value without any UoM may be an integer or floating point number for any type (processes, users, etc.). diff --git a/doc/06-distributed-monitoring.md b/doc/06-distributed-monitoring.md index b05f4a8c8..03c5bfc70 100644 --- a/doc/06-distributed-monitoring.md +++ b/doc/06-distributed-monitoring.md @@ -2205,7 +2205,7 @@ object Zone "icinga2-agent2.localdomain" { The two agent nodes do not need to know about each other. The only important thing is that they know about the parent zone (the satellite) and their endpoint members (and optionally the global zone). -> **Tipp** +> **Tip** > > In the example above we've specified the `host` attribute in the agent endpoint configuration. In this mode, > the satellites actively connect to the agents. This costs some resources on the satellite -- if you prefer to diff --git a/doc/09-object-types.md b/doc/09-object-types.md index 5c802a946..89b6ce102 100644 --- a/doc/09-object-types.md +++ b/doc/09-object-types.md @@ -1134,7 +1134,7 @@ for a more secure configuration is provided by the [Mozilla Wiki](https://wiki.m Ensure to use the same configuration for both attributes on **all** endpoints to avoid communication problems which requires to use `cipher_list` compatible with the endpoint using the oldest version of the OpenSSL library. If using other tools to connect to the API ensure also compatibility with them as this setting affects not only inter-cluster -communcation but also the REST API. +communication but also the REST API. ### CheckerComponent diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md index 45bfa7b53..54b17b15e 100644 --- a/doc/10-icinga-template-library.md +++ b/doc/10-icinga-template-library.md @@ -1317,7 +1317,7 @@ Custom variables passed as [command parameters](03-monitoring-basics.md#command- Name | Description --- | --- rpc_address | **Optional.** The rpc host address. Defaults to "$address$ if the host `address` attribute is set, "$address6$" otherwise. -rpc_command | **Required.** The programm name (or number). +rpc_command | **Required.** The program name (or number). rpc_port | **Optional.** The port that should be checked. rpc_version | **Optional.** The version you want to check for (one or more). rpc_udp | **Optional.** Use UDP test. Defaults to false. @@ -2323,7 +2323,7 @@ snmp_storage_type | **Optional.** Filter by storage type. Valid options ar snmp_perf | **Optional.** Enable perfdata values. Defaults to true. snmp_exclude | **Optional.** Select all storages except the one(s) selected by -m. No action on storage type selection. snmp_timeout | **Optional.** The command timeout in seconds. Defaults to 5 seconds. -snmp_storage_olength | **Optional.** Max-size of the SNMP message, usefull in case of Too Long responses. +snmp_storage_olength | **Optional.** Max-size of the SNMP message, useful in case of Too Long responses. ### snmp-interface @@ -3170,7 +3170,7 @@ specified patterns in log files. Name | Description ----------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ -logfiles_tag | **Optional.** A short unique descriptor for this search. It will appear in the output of the plugin and is used to separare the different services. +logfiles_tag | **Optional.** A short unique descriptor for this search. It will appear in the output of the plugin and is used to separate the different services. logfiles_logfile | **Optional.** This is the name of the log file you want to scan. logfiles_rotation | **Optional.** This is the method how log files are rotated. One of the predefined methods or a regular expression, which helps identify the rotated archives. If this key is missing, check_logfiles assumes that the log file will be simply overwritten instead of rotated. logfiles_critical_pattern | **Optional.** A regular expression which will trigger a critical error. @@ -3954,7 +3954,7 @@ vmware_isregexp | **Optional.** Treat blacklist and whitelist expr vmware_dc_volume_used | **Optional.** Output used space instead of free. Defaults to "true". vmware_warn | **Optional.** The warning threshold for volumes. Defaults to "80%". vmware_crit | **Optional.** The critical threshold for volumes. Defaults to "90%". -vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenace mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. +vmware_maintenance_mode_state | **Optional.** Set status in case ESX host is in maintenance mode. Possible Values are: ok or OK, CRITICAL or critical or CRIT or crit, WARNING or warning or WARN or warn. Default is UNKNOWN because you do not know the real state. Values are case insensitive. **vmware-esx-dc-runtime-info** diff --git a/doc/12-icinga2-api.md b/doc/12-icinga2-api.md index 6cb948132..8ee96aa86 100644 --- a/doc/12-icinga2-api.md +++ b/doc/12-icinga2-api.md @@ -498,7 +498,7 @@ The example below is not valid: -d '{ "type": "Host", "filter": ""linux-servers" in host.groups" }' ``` -The double quotes need to be escaped with a preceeding backslash: +The double quotes need to be escaped with a preceding backslash: ``` -d '{ "type": "Host", "filter": "\"linux-servers\" in host.groups" }' @@ -1073,7 +1073,7 @@ Send a `POST` request to the URL endpoint `/v1/actions/process-check-result`. exit\_status | Number | **Required.** For services: 0=OK, 1=WARNING, 2=CRITICAL, 3=UNKNOWN, for hosts: 0=UP, 1=DOWN. plugin\_output | String | **Required.** One or more lines of the plugin main output. Does **not** contain the performance data. performance\_data | Array|String | **Optional.** The performance data as array of strings. The raw performance data string can be used too. - check\_command | Array|String | **Optional.** The first entry should be the check commands path, then one entry for each command line option followed by an entry for each of its argument. Alternativly a single string can be used. + check\_command | Array|String | **Optional.** The first entry should be the check commands path, then one entry for each command line option followed by an entry for each of its argument. Alternatively a single string can be used. check\_source | String | **Optional.** Usually the name of the `command_endpoint` execution\_start | Timestamp | **Optional.** The timestamp where a script/process started its execution. execution\_end | Timestamp | **Optional.** The timestamp where a script/process ended its execution. This timestamp is used in features to determine e.g. the metric timestamp. @@ -2019,7 +2019,7 @@ validate the configuration asynchronously and populate a status log which can be fetched in a separated request. Once the validation succeeds, a reload is triggered by default. -This functionality was primarly developed for the [Icinga Director](https://icinga.com/docs/director/latest/) +This functionality was primarily developed for the [Icinga Director](https://icinga.com/docs/director/latest/) but can be used with your own deployments too. It also solves the problem with certain runtime objects (zones, endpoints) and can be used to deploy global templates in [global cluster zones](06-distributed-monitoring.md#distributed-monitoring-global-zone-config-sync). diff --git a/doc/14-features.md b/doc/14-features.md index 31c696619..843aa20f4 100644 --- a/doc/14-features.md +++ b/doc/14-features.md @@ -246,7 +246,7 @@ resolved, it will be dropped and not sent to the target host. Backslashes are allowed in tag keys, tag values and field keys, however they are also escape characters when followed by a space or comma, but cannot be escaped themselves. -As a result all trailling slashes in these fields are replaced with an underscore. This +As a result all trailing slashes in these fields are replaced with an underscore. This predominantly affects Windows paths e.g. `C:\` becomes `C:_`. The database/bucket is assumed to exist so this object will make no attempt to create it currently. @@ -396,7 +396,7 @@ check_result.perfdata..warn check_result.perfdata..crit ``` -Additionaly it is possible to configure custom tags that are applied to the metrics via `host_tags_template` or `service_tags_template`. +Additionally it is possible to configure custom tags that are applied to the metrics via `host_tags_template` or `service_tags_template`. Depending on whether the write event was triggered on a service or host object, additional tags are added to the ElasticSearch entries. A host metrics entry configured with the following `host_tags_template`: @@ -574,7 +574,7 @@ with the following tags Functionality exists to modify the built in OpenTSDB metric names that the plugin writes to. By default this is `icinga.host` and `icinga.service.`. -These prefixes can be modified as necessary to any arbitary string. The prefix +These prefixes can be modified as necessary to any arbitrary string. The prefix configuration also supports Icinga macros, so if you rather use `` or any other variable instead of `` you may do so. diff --git a/doc/15-troubleshooting.md b/doc/15-troubleshooting.md index 67f2de65a..67ba70cb2 100644 --- a/doc/15-troubleshooting.md +++ b/doc/15-troubleshooting.md @@ -42,7 +42,7 @@ is also key to identify bottlenecks and issues. > > [Monitor Icinga 2](08-advanced-topics.md#monitoring-icinga) and use the hints for further analysis. -* Analyze the system's performance and dentify bottlenecks and issues. +* Analyze the system's performance and identify bottlenecks and issues. * Collect details about all applications (e.g. Icinga 2, MySQL, Apache, Graphite, Elastic, etc.). * If data is exchanged via network (e.g. central MySQL cluster) ensure to monitor the bandwidth capabilities too. * Add graphs from Grafana or Graphite as screenshots to your issue description diff --git a/doc/16-upgrading-icinga-2.md b/doc/16-upgrading-icinga-2.md index 2c5a1d0a5..bdff71ec4 100644 --- a/doc/16-upgrading-icinga-2.md +++ b/doc/16-upgrading-icinga-2.md @@ -128,7 +128,7 @@ have been removed from the command and documentation. ### Bugfixes for 2.11 2.11.1 on agents/satellites fixes a problem where 2.10.x as config master would send out an unwanted config marker file, -thus rendering the agent to think it is autoritative for the config, and never accepting any new +thus rendering the agent to think it is authoritative for the config, and never accepting any new config files for the zone(s). **If your config master is 2.11.x already, you are not affected by this problem.** In order to fix this, upgrade to at least 2.11.1, and purge away the local config sync storage once, then restart. @@ -390,7 +390,7 @@ This affects the following features: The reconnect failover has been improved, and the default `failover_timeout` for the DB IDO features has been lowered from 60 to 30 seconds. Object authority updates (required for balancing in the cluster) happen -more frequenty (was 30, is 10 seconds). +more frequently (was 30, is 10 seconds). Also the cold startup without object authority updates has been reduced from 60 to 30 seconds. This is to allow cluster reconnects (lowered from 60s to 10s in 2.10) before actually considering a failover/split brain scenario. diff --git a/doc/17-language-reference.md b/doc/17-language-reference.md index 53bea28a3..bdc236e1f 100644 --- a/doc/17-language-reference.md +++ b/doc/17-language-reference.md @@ -666,7 +666,7 @@ setting the `check_command` attribute or custom variables as command parameters. and afterwards the `assign where` and `ignore where` conditions are evaluated. It is not necessary to check attributes referenced in the `for loop` expression -for their existance using an additional `assign where` condition. +for their existence using an additional `assign where` condition. More usage examples are documented in the [monitoring basics](03-monitoring-basics.md#using-apply-for) chapter. diff --git a/doc/22-selinux.md b/doc/22-selinux.md index ae06037f2..0eb337345 100644 --- a/doc/22-selinux.md +++ b/doc/22-selinux.md @@ -122,7 +122,7 @@ Having this boolean enabled allows icinga2 to connect to all ports. This can be **icinga2_run_sudo** -To allow Icinga 2 executing plugins via sudo you can toogle this boolean. It is disabled by default, resulting in error messages like `execvpe(sudo) failed: Permission denied`. +To allow Icinga 2 executing plugins via sudo you can toggle this boolean. It is disabled by default, resulting in error messages like `execvpe(sudo) failed: Permission denied`. **httpd_can_write_icinga2_command** @@ -204,7 +204,7 @@ If you restart the daemon now it will successfully connect to graphite. #### Running plugins requiring sudo -Some plugins require privileged access to the system and are designied to be executed via `sudo` to get these privileges. +Some plugins require privileged access to the system and are designed to be executed via `sudo` to get these privileges. In this case it is the CheckCommand [running_kernel](10-icinga-template-library.md#plugin-contrib-command-running_kernel) which is set to use `sudo`. @@ -219,7 +219,7 @@ In this case it is the CheckCommand [running_kernel](10-icinga-template-library. assign where host.name == NodeName } -Having this Service defined will result in a UNKNOWN state and the error message `execvpe(sudo) failed: Permission denied` because SELinux dening the execution. +Having this Service defined will result in a UNKNOWN state and the error message `execvpe(sudo) failed: Permission denied` because SELinux denying the execution. Switching the boolean `icinga2_run_sudo` to allow the execution will result in the check executed successfully. @@ -229,7 +229,7 @@ Switching the boolean `icinga2_run_sudo` to allow the execution will result in t #### Confining a user If you want to have an administrative account capable of only managing icinga2 and not the complete system, you can restrict the privileges by confining -this user. This is completly optional! +this user. This is completely optional! Start by adding the Icinga 2 administrator role `icinga2adm_r` to the administrative SELinux user `staff_u`. @@ -295,7 +295,7 @@ Failed to issue method call: Access denied If you experience any problems while running in enforcing mode try to reproduce it in permissive mode. If the problem persists it is not related to SELinux because in permissive mode SELinux will not deny anything. -After some feedback Icinga 2 is now running in a enforced domain, but still adds also some rules for other necessary services so no problems should occure at all. But you can help to enhance the policy by testing Icinga 2 running confined by SELinux. +After some feedback Icinga 2 is now running in a enforced domain, but still adds also some rules for other necessary services so no problems should occur at all. But you can help to enhance the policy by testing Icinga 2 running confined by SELinux. Please add the following information to [bug reports](https://icinga.com/community/): From b7deb5099ae697e5ab066402b11166a997f53104 Mon Sep 17 00:00:00 2001 From: Markus Opolka Date: Mon, 30 Jun 2025 16:55:18 +0200 Subject: [PATCH 367/415] Update AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 6b648735c..3f159b7ea 100644 --- a/AUTHORS +++ b/AUTHORS @@ -180,6 +180,7 @@ Marius Bergmann Marius Sturm Mark Leary Markus Frosch +Markus Opolka Markus Waldmüller Markus Weber Martijn van Duren From 950c8017df7abdaa213d7b0133a3409165a99863 Mon Sep 17 00:00:00 2001 From: Alvar Penning Date: Wed, 18 Jun 2025 10:49:30 +0200 Subject: [PATCH 368/415] docs: Icinga DB Setup for every Distribution Within the GNU/Linux distribution specific installation guides, the "Set up Icinga DB" section was only excluded for openSUSE. However, since there is an openSUSE installation guide within Icinga DB[^0], this is not consistent. Thus, the if-guard was removed, resulting in this section being available for each distribution. Windows is already excluded through an if-guard above. Some cases for Fedora were missing, which were also added. [^0]: https://icinga.com/docs/icinga-db/latest/doc/02-Installation/openSUSE/ --- doc/02-installation.md | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/doc/02-installation.md b/doc/02-installation.md index 5c1091fa3..d369f5111 100644 --- a/doc/02-installation.md +++ b/doc/02-installation.md @@ -369,7 +369,6 @@ Restart Icinga 2 for these changes to take effect. systemctl restart icinga2 ``` - ## Set up Icinga DB Icinga DB is a set of components for publishing, synchronizing and @@ -444,9 +443,18 @@ dnf install icingadb-redis ``` - + -##### SLES +##### Fedora + +```bash +dnf install icingadb-redis +``` + + + + +##### SLES / openSUSE ```bash zypper install icingadb-redis @@ -537,6 +545,9 @@ you have completed the instructions here and can proceed to [install the Icinga DB daemon on Debian](https://icinga.com/docs/icinga-db/latest/doc/02-Installation/Debian/#installing-the-package), + +[install the Icinga DB daemon on Fedora](https://icinga.com/docs/icinga-db/latest/doc/02-Installation/Fedora/#installing-the-package), + [install the Icinga DB daemon on RHEL](https://icinga.com/docs/icinga-db/latest/doc/02-Installation/RHEL/#installing-the-package), @@ -546,8 +557,10 @@ you have completed the instructions here and can proceed to [install the Icinga DB daemon on Ubuntu](https://icinga.com/docs/icinga-db/latest/doc/02-Installation/Ubuntu/#installing-the-package), -which will also guide you through the setup of the database and Icinga DB Web. + +[install the Icinga DB daemon on openSUSE](https://icinga.com/docs/icinga-db/latest/doc/02-Installation/openSUSE/#installing-the-package), +which will also guide you through the setup of the database and Icinga DB Web. ## Backup From 0ebcd2662d86ad3587d32353e970b64e564df4cf Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Tue, 8 Jul 2025 13:52:40 +0200 Subject: [PATCH 369/415] No longer allow overriding the frozen attribute of containers The Array, Dictionary, and Namespace types provide a Freeze() method that makes them read-only. So far, there was the possibility to call some methods with `overrideFrozen=true` which would then bypass the corresponding check and allow modification of the data structures nonetheless. With 24b57f0d3a222835178e88489eabd595755ed883, this possibility was already removed from the Namespace type. However, for interface compatibility, it kept the parameter and just ignores it, throwing an exception on any modification on a frozen instance. The only place using `overrideFrozen` was processing of the `-D`/`--define` command line flag that allows setting additional variables in the DSL. At the time it is evaluated, there are no user-created data structures yet that could be frozen, so the only frozen objects that could be encountered are Namespaces (Icinga doesn't freeze other types by itself) and for these, `overrideFrozen` already has no effect. Hence, there is no harm in removing `overrideFrozen` altogether. This simplifies the code and also means that frozen objects are now indeed read-only without exceptions, allowing further optimizations regarding locking in the future. --- icinga-app/icinga.cpp | 2 - lib/base/array.cpp | 52 +++++++++++-------------- lib/base/array.hpp | 22 +++++------ lib/base/dictionary.cpp | 9 ++--- lib/base/dictionary.hpp | 4 +- lib/base/namespace.cpp | 7 +--- lib/base/namespace.hpp | 2 +- lib/base/object.cpp | 2 +- lib/base/object.hpp | 2 +- lib/base/reference.cpp | 2 +- lib/config/expression.cpp | 14 +------ lib/config/expression.hpp | 7 ---- lib/config/vmops.hpp | 4 +- test/icinga-notification.cpp | 6 +-- test/methods-pluginnotificationtask.cpp | 6 +-- 15 files changed, 55 insertions(+), 86 deletions(-) diff --git a/icinga-app/icinga.cpp b/icinga-app/icinga.cpp index d4e27c566..63b51b77d 100644 --- a/icinga-app/icinga.cpp +++ b/icinga-app/icinga.cpp @@ -420,12 +420,10 @@ static int Main() for (size_t i = 1; i < keyTokens.size(); i++) { std::unique_ptr indexerExpr{new IndexerExpression(std::move(expr), MakeLiteral(keyTokens[i]))}; - indexerExpr->SetOverrideFrozen(); expr = std::move(indexerExpr); } std::unique_ptr setExpr{new SetExpression(std::move(expr), OpSetLiteral, MakeLiteral(value))}; - setExpr->SetOverrideFrozen(); try { ScriptFrame frame(true); diff --git a/lib/base/array.cpp b/lib/base/array.cpp index 08e06fad2..73adcb279 100644 --- a/lib/base/array.cpp +++ b/lib/base/array.cpp @@ -45,13 +45,12 @@ Value Array::Get(SizeType index) const * * @param index The index. * @param value The value. - * @param overrideFrozen Whether to allow modifying frozen arrays. */ -void Array::Set(SizeType index, const Value& value, bool overrideFrozen) +void Array::Set(SizeType index, const Value& value) { ObjectLock olock(this); - if (m_Frozen && !overrideFrozen) + if (m_Frozen) BOOST_THROW_EXCEPTION(std::invalid_argument("Value in array must not be modified.")); m_Data.at(index) = value; @@ -62,13 +61,12 @@ void Array::Set(SizeType index, const Value& value, bool overrideFrozen) * * @param index The index. * @param value The value. - * @param overrideFrozen Whether to allow modifying frozen arrays. */ -void Array::Set(SizeType index, Value&& value, bool overrideFrozen) +void Array::Set(SizeType index, Value&& value) { ObjectLock olock(this); - if (m_Frozen && !overrideFrozen) + if (m_Frozen) BOOST_THROW_EXCEPTION(std::invalid_argument("Array must not be modified.")); m_Data.at(index).Swap(value); @@ -78,13 +76,12 @@ void Array::Set(SizeType index, Value&& value, bool overrideFrozen) * Adds a value to the array. * * @param value The value. - * @param overrideFrozen Whether to allow modifying frozen arrays. */ -void Array::Add(Value value, bool overrideFrozen) +void Array::Add(Value value) { ObjectLock olock(this); - if (m_Frozen && !overrideFrozen) + if (m_Frozen) BOOST_THROW_EXCEPTION(std::invalid_argument("Array must not be modified.")); m_Data.push_back(std::move(value)); @@ -148,15 +145,14 @@ bool Array::Contains(const Value& value) const * * @param index The index * @param value The value to add - * @param overrideFrozen Whether to allow modifying frozen arrays. */ -void Array::Insert(SizeType index, Value value, bool overrideFrozen) +void Array::Insert(SizeType index, Value value) { ObjectLock olock(this); ASSERT(index <= m_Data.size()); - if (m_Frozen && !overrideFrozen) + if (m_Frozen) BOOST_THROW_EXCEPTION(std::invalid_argument("Array must not be modified.")); m_Data.insert(m_Data.begin() + index, std::move(value)); @@ -166,13 +162,12 @@ void Array::Insert(SizeType index, Value value, bool overrideFrozen) * Removes the specified index from the array. * * @param index The index. - * @param overrideFrozen Whether to allow modifying frozen arrays. */ -void Array::Remove(SizeType index, bool overrideFrozen) +void Array::Remove(SizeType index) { ObjectLock olock(this); - if (m_Frozen && !overrideFrozen) + if (m_Frozen) BOOST_THROW_EXCEPTION(std::invalid_argument("Array must not be modified.")); if (index >= m_Data.size()) @@ -185,43 +180,42 @@ void Array::Remove(SizeType index, bool overrideFrozen) * Removes the item specified by the iterator from the array. * * @param it The iterator. - * @param overrideFrozen Whether to allow modifying frozen arrays. */ -void Array::Remove(Array::Iterator it, bool overrideFrozen) +void Array::Remove(Array::Iterator it) { ASSERT(OwnsLock()); - if (m_Frozen && !overrideFrozen) + if (m_Frozen) BOOST_THROW_EXCEPTION(std::invalid_argument("Array must not be modified.")); m_Data.erase(it); } -void Array::Resize(SizeType newSize, bool overrideFrozen) +void Array::Resize(SizeType newSize) { ObjectLock olock(this); - if (m_Frozen && !overrideFrozen) + if (m_Frozen) BOOST_THROW_EXCEPTION(std::invalid_argument("Array must not be modified.")); m_Data.resize(newSize); } -void Array::Clear(bool overrideFrozen) +void Array::Clear() { ObjectLock olock(this); - if (m_Frozen && !overrideFrozen) + if (m_Frozen) BOOST_THROW_EXCEPTION(std::invalid_argument("Array must not be modified.")); m_Data.clear(); } -void Array::Reserve(SizeType newSize, bool overrideFrozen) +void Array::Reserve(SizeType newSize) { ObjectLock olock(this); - if (m_Frozen && !overrideFrozen) + if (m_Frozen) BOOST_THROW_EXCEPTION(std::invalid_argument("Array must not be modified.")); m_Data.reserve(newSize); @@ -280,11 +274,11 @@ Array::Ptr Array::Reverse() const return result; } -void Array::Sort(bool overrideFrozen) +void Array::Sort() { ObjectLock olock(this); - if (m_Frozen && !overrideFrozen) + if (m_Frozen) BOOST_THROW_EXCEPTION(std::invalid_argument("Array must not be modified.")); std::sort(m_Data.begin(), m_Data.end()); @@ -354,7 +348,7 @@ Value Array::GetFieldByName(const String& field, bool sandboxed, const DebugInfo return Get(index); } -void Array::SetFieldByName(const String& field, const Value& value, bool overrideFrozen, const DebugInfo& debugInfo) +void Array::SetFieldByName(const String& field, const Value& value, const DebugInfo& debugInfo) { ObjectLock olock(this); @@ -364,9 +358,9 @@ void Array::SetFieldByName(const String& field, const Value& value, bool overrid BOOST_THROW_EXCEPTION(ScriptError("Array index '" + Convert::ToString(index) + "' is out of bounds.", debugInfo)); if (static_cast(index) >= GetLength()) - Resize(index + 1, overrideFrozen); + Resize(index + 1); - Set(index, value, overrideFrozen); + Set(index, value); } Array::Iterator icinga::begin(const Array::Ptr& x) diff --git a/lib/base/array.hpp b/lib/base/array.hpp index 2c9a9dda7..9589d1901 100644 --- a/lib/base/array.hpp +++ b/lib/base/array.hpp @@ -38,9 +38,9 @@ public: Array(std::initializer_list init); Value Get(SizeType index) const; - void Set(SizeType index, const Value& value, bool overrideFrozen = false); - void Set(SizeType index, Value&& value, bool overrideFrozen = false); - void Add(Value value, bool overrideFrozen = false); + void Set(SizeType index, const Value& value); + void Set(SizeType index, Value&& value); + void Add(Value value); Iterator Begin(); Iterator End(); @@ -48,14 +48,14 @@ public: size_t GetLength() const; bool Contains(const Value& value) const; - void Insert(SizeType index, Value value, bool overrideFrozen = false); - void Remove(SizeType index, bool overrideFrozen = false); - void Remove(Iterator it, bool overrideFrozen = false); + void Insert(SizeType index, Value value); + void Remove(SizeType index); + void Remove(Iterator it); - void Resize(SizeType newSize, bool overrideFrozen = false); - void Clear(bool overrideFrozen = false); + void Resize(SizeType newSize); + void Clear(); - void Reserve(SizeType newSize, bool overrideFrozen = false); + void Reserve(SizeType newSize); void CopyTo(const Array::Ptr& dest) const; Array::Ptr ShallowClone() const; @@ -91,7 +91,7 @@ public: Array::Ptr Reverse() const; - void Sort(bool overrideFrozen = false); + void Sort(); String ToString() const override; Value Join(const Value& separator) const; @@ -100,7 +100,7 @@ public: void Freeze(); Value GetFieldByName(const String& field, bool sandboxed, const DebugInfo& debugInfo) const override; - void SetFieldByName(const String& field, const Value& value, bool overrideFrozen, const DebugInfo& debugInfo) override; + void SetFieldByName(const String& field, const Value& value, const DebugInfo& debugInfo) override; private: std::vector m_Data; /**< The data for the array. */ diff --git a/lib/base/dictionary.cpp b/lib/base/dictionary.cpp index 43df4af8f..89b6c4cbc 100644 --- a/lib/base/dictionary.cpp +++ b/lib/base/dictionary.cpp @@ -86,14 +86,13 @@ const Value * Dictionary::GetRef(const String& key) const * * @param key The key. * @param value The value. - * @param overrideFrozen Whether to allow modifying frozen dictionaries. */ -void Dictionary::Set(const String& key, Value value, bool overrideFrozen) +void Dictionary::Set(const String& key, Value value) { ObjectLock olock(this); std::unique_lock lock (m_DataMutex); - if (m_Frozen && !overrideFrozen) + if (m_Frozen) BOOST_THROW_EXCEPTION(std::invalid_argument("Value in dictionary must not be modified.")); m_Data[key] = std::move(value); @@ -290,9 +289,9 @@ Value Dictionary::GetFieldByName(const String& field, bool, const DebugInfo& deb return GetPrototypeField(const_cast(this), field, false, debugInfo); } -void Dictionary::SetFieldByName(const String& field, const Value& value, bool overrideFrozen, const DebugInfo&) +void Dictionary::SetFieldByName(const String& field, const Value& value, const DebugInfo&) { - Set(field, value, overrideFrozen); + Set(field, value); } bool Dictionary::HasOwnField(const String& field) const diff --git a/lib/base/dictionary.hpp b/lib/base/dictionary.hpp index ffccd630f..ba2fbe82c 100644 --- a/lib/base/dictionary.hpp +++ b/lib/base/dictionary.hpp @@ -43,7 +43,7 @@ public: Value Get(const String& key) const; bool Get(const String& key, Value *result) const; const Value * GetRef(const String& key) const; - void Set(const String& key, Value value, bool overrideFrozen = false); + void Set(const String& key, Value value); bool Contains(const String& key) const; Iterator Begin(); @@ -71,7 +71,7 @@ public: void Freeze(); Value GetFieldByName(const String& field, bool sandboxed, const DebugInfo& debugInfo) const override; - void SetFieldByName(const String& field, const Value& value, bool overrideFrozen, const DebugInfo& debugInfo) override; + void SetFieldByName(const String& field, const Value& value, const DebugInfo& debugInfo) override; bool HasOwnField(const String& field) const override; bool GetOwnField(const String& field, Value *result) const override; diff --git a/lib/base/namespace.cpp b/lib/base/namespace.cpp index c3b859cff..1f53efc92 100644 --- a/lib/base/namespace.cpp +++ b/lib/base/namespace.cpp @@ -143,13 +143,8 @@ Value Namespace::GetFieldByName(const String& field, bool, const DebugInfo& debu return GetPrototypeField(const_cast(this), field, false, debugInfo); /* Ignore indexer not found errors similar to the Dictionary class. */ } -void Namespace::SetFieldByName(const String& field, const Value& value, bool overrideFrozen, const DebugInfo& debugInfo) +void Namespace::SetFieldByName(const String& field, const Value& value, const DebugInfo& debugInfo) { - // The override frozen parameter is mandated by the interface but ignored here. If the namespace is frozen, this - // disables locking for read operations, so it must not be modified again to ensure the consistency of the internal - // data structures. - (void) overrideFrozen; - Set(field, value, false, debugInfo); } diff --git a/lib/base/namespace.hpp b/lib/base/namespace.hpp index 94f2055d3..1a028e2c5 100644 --- a/lib/base/namespace.hpp +++ b/lib/base/namespace.hpp @@ -80,7 +80,7 @@ public: size_t GetLength() const; Value GetFieldByName(const String& field, bool sandboxed, const DebugInfo& debugInfo) const override; - void SetFieldByName(const String& field, const Value& value, bool overrideFrozen, const DebugInfo& debugInfo) override; + void SetFieldByName(const String& field, const Value& value, const DebugInfo& debugInfo) override; bool HasOwnField(const String& field) const override; bool GetOwnField(const String& field, Value *result) const override; diff --git a/lib/base/object.cpp b/lib/base/object.cpp index 92a43b912..5c7c67a8e 100644 --- a/lib/base/object.cpp +++ b/lib/base/object.cpp @@ -125,7 +125,7 @@ Value Object::GetFieldByName(const String& field, bool sandboxed, const DebugInf return GetField(fid); } -void Object::SetFieldByName(const String& field, const Value& value, bool overrideFrozen, const DebugInfo& debugInfo) +void Object::SetFieldByName(const String& field, const Value& value, const DebugInfo& debugInfo) { Type::Ptr type = GetReflectionType(); diff --git a/lib/base/object.hpp b/lib/base/object.hpp index 008426b8d..7f520672e 100644 --- a/lib/base/object.hpp +++ b/lib/base/object.hpp @@ -171,7 +171,7 @@ public: virtual void SetField(int id, const Value& value, bool suppress_events = false, const Value& cookie = Empty); virtual Value GetField(int id) const; virtual Value GetFieldByName(const String& field, bool sandboxed, const DebugInfo& debugInfo) const; - virtual void SetFieldByName(const String& field, const Value& value, bool overrideFrozen, const DebugInfo& debugInfo); + virtual void SetFieldByName(const String& field, const Value& value, const DebugInfo& debugInfo); virtual bool HasOwnField(const String& field) const; virtual bool GetOwnField(const String& field, Value *result) const; virtual void ValidateField(int id, const Lazy& lvalue, const ValidationUtils& utils); diff --git a/lib/base/reference.cpp b/lib/base/reference.cpp index b0104af6c..9382bde30 100644 --- a/lib/base/reference.cpp +++ b/lib/base/reference.cpp @@ -24,7 +24,7 @@ Value Reference::Get() const void Reference::Set(const Value& value) { - m_Parent->SetFieldByName(m_Index, value, false, DebugInfo()); + m_Parent->SetFieldByName(m_Index, value, DebugInfo()); } Object::Ptr Reference::GetParent() const diff --git a/lib/config/expression.cpp b/lib/config/expression.cpp index 09b860cde..bec121ec3 100644 --- a/lib/config/expression.cpp +++ b/lib/config/expression.cpp @@ -652,7 +652,7 @@ ExpressionResult SetExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) } } - VMOps::SetField(parent, index, operand2.GetValue(), m_OverrideFrozen, m_DebugInfo); + VMOps::SetField(parent, index, operand2.GetValue(), m_DebugInfo); if (psdhint) { psdhint->AddMessage("=", m_DebugInfo); @@ -666,11 +666,6 @@ ExpressionResult SetExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) return Empty; } -void SetExpression::SetOverrideFrozen() -{ - m_OverrideFrozen = true; -} - ExpressionResult SetConstExpression::DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const { auto globals = ScriptGlobal::GetGlobals(); @@ -772,7 +767,7 @@ bool IndexerExpression::GetReference(ScriptFrame& frame, bool init_dict, Value * old_value = VMOps::GetField(vparent, vindex, frame.Sandboxed, m_Operand1->GetDebugInfo()); if (old_value.IsEmpty() && !old_value.IsString()) - VMOps::SetField(vparent, vindex, new Dictionary(), m_OverrideFrozen, m_Operand1->GetDebugInfo()); + VMOps::SetField(vparent, vindex, new Dictionary(), m_Operand1->GetDebugInfo()); } *parent = VMOps::GetField(vparent, vindex, frame.Sandboxed, m_DebugInfo); @@ -798,11 +793,6 @@ bool IndexerExpression::GetReference(ScriptFrame& frame, bool init_dict, Value * return true; } -void IndexerExpression::SetOverrideFrozen() -{ - m_OverrideFrozen = true; -} - void icinga::BindToScope(std::unique_ptr& expr, ScopeSpecifier scopeSpec) { auto *dexpr = dynamic_cast(expr.get()); diff --git a/lib/config/expression.hpp b/lib/config/expression.hpp index 644548d28..915da40c5 100644 --- a/lib/config/expression.hpp +++ b/lib/config/expression.hpp @@ -657,14 +657,11 @@ public: : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo), m_Op(op) { } - void SetOverrideFrozen(); - protected: ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; private: CombinedSetOp m_Op; - bool m_OverrideFrozen{false}; friend void BindToScope(std::unique_ptr& expr, ScopeSpecifier scopeSpec); }; @@ -755,11 +752,7 @@ public: : BinaryExpression(std::move(operand1), std::move(operand2), debugInfo) { } - void SetOverrideFrozen(); - protected: - bool m_OverrideFrozen{false}; - ExpressionResult DoEvaluate(ScriptFrame& frame, DebugHint *dhint) const override; bool GetReference(ScriptFrame& frame, bool init_dict, Value *parent, String *index, DebugHint **dhint) const override; diff --git a/lib/config/vmops.hpp b/lib/config/vmops.hpp index ea3098359..c6ba03bcc 100644 --- a/lib/config/vmops.hpp +++ b/lib/config/vmops.hpp @@ -246,12 +246,12 @@ public: return object->GetFieldByName(field, sandboxed, debugInfo); } - static inline void SetField(const Object::Ptr& context, const String& field, const Value& value, bool overrideFrozen, const DebugInfo& debugInfo = DebugInfo()) + static inline void SetField(const Object::Ptr& context, const String& field, const Value& value, const DebugInfo& debugInfo = DebugInfo()) { if (!context) BOOST_THROW_EXCEPTION(ScriptError("Cannot set field '" + field + "' on a value that is not an object.", debugInfo)); - return context->SetFieldByName(field, value, overrideFrozen, debugInfo); + return context->SetFieldByName(field, value, debugInfo); } private: diff --git a/test/icinga-notification.cpp b/test/icinga-notification.cpp index a0aeb7df8..5b32178ba 100644 --- a/test/icinga-notification.cpp +++ b/test/icinga-notification.cpp @@ -36,9 +36,9 @@ struct DuplicateDueToFilterHelper nc->SetExecute(new Function("", [this]() { ++called; }), true); nc->Register(); - n->SetFieldByName("host_name", "example.com", false, DebugInfo()); - n->SetFieldByName("service_name", "disk", false, DebugInfo()); - n->SetFieldByName("command", "mail", false, DebugInfo()); + n->SetFieldByName("host_name", "example.com", DebugInfo()); + n->SetFieldByName("service_name", "disk", DebugInfo()); + n->SetFieldByName("command", "mail", DebugInfo()); n->SetUsersRaw(new Array({"jdoe"}), true); n->SetTypeFilter(typeFilter); n->SetStateFilter(stateFilter); diff --git a/test/methods-pluginnotificationtask.cpp b/test/methods-pluginnotificationtask.cpp index ec582dc8a..d1db38722 100644 --- a/test/methods-pluginnotificationtask.cpp +++ b/test/methods-pluginnotificationtask.cpp @@ -56,9 +56,9 @@ BOOST_AUTO_TEST_CASE(truncate_long_output) nc->SetName("mail", true); nc->Register(); - n->SetFieldByName("host_name", "example.com", false, DebugInfo()); - n->SetFieldByName("service_name", "disk", false, DebugInfo()); - n->SetFieldByName("command", "mail", false, DebugInfo()); + n->SetFieldByName("host_name", "example.com", DebugInfo()); + n->SetFieldByName("service_name", "disk", DebugInfo()); + n->SetFieldByName("command", "mail", DebugInfo()); n->OnAllConfigLoaded(); // link Service Checkable::ExecuteCommandProcessFinishedHandler = [&promise](const Value&, const ProcessResult& pr) { From 455d6fcde1765e69751db20694c13ca1bbc68bcf Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 1 Jul 2025 09:49:31 +0200 Subject: [PATCH 370/415] Introduce `ValueGenerator` class --- lib/base/CMakeLists.txt | 1 + lib/base/generator.hpp | 48 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) create mode 100644 lib/base/generator.hpp diff --git a/lib/base/CMakeLists.txt b/lib/base/CMakeLists.txt index d44254a35..1bb700c3d 100644 --- a/lib/base/CMakeLists.txt +++ b/lib/base/CMakeLists.txt @@ -37,6 +37,7 @@ set(base_SOURCES fifo.cpp fifo.hpp filelogger.cpp filelogger.hpp filelogger-ti.hpp function.cpp function.hpp function-ti.hpp function-script.cpp functionwrapper.hpp + generator.hpp initialize.cpp initialize.hpp intrusive-ptr.hpp io-engine.cpp io-engine.hpp diff --git a/lib/base/generator.hpp b/lib/base/generator.hpp new file mode 100644 index 000000000..fa41e67fc --- /dev/null +++ b/lib/base/generator.hpp @@ -0,0 +1,48 @@ +/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */ + +#pragma once + +#include "base/i2-base.hpp" +#include "base/value.hpp" +#include + +namespace icinga +{ + +/** + * ValueGenerator is a class that defines a generator function type for producing Values on demand. + * + * This class is used to create generator functions that can yield any values that can be represented by the + * Icinga Value type. The generator function is exhausted when it returns `std::nullopt`, indicating that there + * are no more values to produce. Subsequent calls to `Next()` will always return `std::nullopt` after exhaustion. + * + * @ingroup base + */ +class ValueGenerator final : public Object +{ +public: + DECLARE_PTR_TYPEDEFS(ValueGenerator); + + /** + * Generates a Value using the provided generator function. + * + * The generator function should return an `std::optional` which contains the produced Value or + * `std::nullopt` when there are no more values to produce. After the generator function returns `std::nullopt`, + * the generator is considered exhausted, and further calls to `Next()` will always return `std::nullopt`. + */ + using GenFunc = std::function()>; + + explicit ValueGenerator(GenFunc generator): m_Generator(std::move(generator)) + { + } + + std::optional Next() const + { + return m_Generator(); + } + +private: + GenFunc m_Generator; // The generator function that produces Values. +}; + +} From 4c0628c24de529da3575ddbf4c10a02f81d3ac9e Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 3 Jul 2025 12:14:31 +0200 Subject: [PATCH 371/415] Allow to defer lock on `ObjectLock` --- lib/base/objectlock.cpp | 12 ++++++++++++ lib/base/objectlock.hpp | 1 + 2 files changed, 13 insertions(+) diff --git a/lib/base/objectlock.cpp b/lib/base/objectlock.cpp index fc0c7c631..fad59160b 100644 --- a/lib/base/objectlock.cpp +++ b/lib/base/objectlock.cpp @@ -18,6 +18,18 @@ ObjectLock::ObjectLock(const Object::Ptr& object) { } +/** + * Constructs a lock for the given object without locking it immediately. + * + * The user must call Lock() explicitly when needed. + * + * @param object The object to lock. + */ +ObjectLock::ObjectLock(const Object::Ptr& object, std::defer_lock_t) + : m_Object(object.get()), m_Locked(false) +{ +} + ObjectLock::ObjectLock(const Object *object) : m_Object(object), m_Locked(false) { diff --git a/lib/base/objectlock.hpp b/lib/base/objectlock.hpp index 8e98641db..abd071c66 100644 --- a/lib/base/objectlock.hpp +++ b/lib/base/objectlock.hpp @@ -15,6 +15,7 @@ struct ObjectLock { public: ObjectLock(const Object::Ptr& object); + ObjectLock(const Object::Ptr& object, std::defer_lock_t); ObjectLock(const Object *object); ObjectLock(const ObjectLock&) = delete; From 8ef921aa5e36080b83fdd3fb9acda73afa05123e Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 8 Jul 2025 17:23:34 +0200 Subject: [PATCH 372/415] Implement bool operator for `ObjectLock` --- lib/base/objectlock.cpp | 12 ++++++++++++ lib/base/objectlock.hpp | 2 ++ 2 files changed, 14 insertions(+) diff --git a/lib/base/objectlock.cpp b/lib/base/objectlock.cpp index fad59160b..9ed9d1f21 100644 --- a/lib/base/objectlock.cpp +++ b/lib/base/objectlock.cpp @@ -65,3 +65,15 @@ void ObjectLock::Unlock() m_Locked = false; } } + +/** + * Returns true if the object is locked, false otherwise. + * + * This operator allows using ObjectLock in boolean contexts. + * + * @returns true if the object is locked, false otherwise. + */ +ObjectLock::operator bool() const +{ + return m_Locked; +} diff --git a/lib/base/objectlock.hpp b/lib/base/objectlock.hpp index abd071c66..328887425 100644 --- a/lib/base/objectlock.hpp +++ b/lib/base/objectlock.hpp @@ -26,6 +26,8 @@ public: void Lock(); void Unlock(); + operator bool() const; + private: const Object *m_Object{nullptr}; bool m_Locked{false}; From 1c61bced03e68096fc467c5f806bff9f08373dcd Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 3 Jul 2025 13:40:10 +0200 Subject: [PATCH 373/415] Introduce `AsyncJsonWriter` output adapter interface --- lib/base/json.cpp | 1 - lib/base/json.hpp | 30 ++++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/lib/base/json.cpp b/lib/base/json.cpp index 56893308a..2d48970bb 100644 --- a/lib/base/json.cpp +++ b/lib/base/json.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include diff --git a/lib/base/json.hpp b/lib/base/json.hpp index df0ea18a0..3df6e797e 100644 --- a/lib/base/json.hpp +++ b/lib/base/json.hpp @@ -4,10 +4,40 @@ #define JSON_H #include "base/i2-base.hpp" +#include +#include namespace icinga { +/** + * AsyncJsonWriter allows writing JSON data to any output stream asynchronously. + * + * All users of this class must ensure that the underlying output stream will not perform any asynchronous I/O + * operations when the @c write_character() or @c write_characters() methods are called. They shall only perform + * such ops when the @c JsonEncoder allows them to do so by calling the @c Flush() method. + * + * @ingroup base + */ +class AsyncJsonWriter : public nlohmann::detail::output_adapter_protocol +{ +public: + /** + * Flush instructs the underlying output stream to write any buffered data to wherever it is supposed to go. + * + * The @c JsonEncoder allows the stream to even perform asynchronous operations in a safe manner by calling + * this method with a dedicated @c boost::asio::yield_context object. The stream must not perform any async + * I/O operations triggered by methods other than this one. Any attempt to do so will result in undefined behavior. + * + * However, this doesn't necessarily enforce the stream to really flush its data immediately, but it's up + * to the implementation to do whatever it needs to. The encoder just gives it a chance to do so by calling + * this method. + * + * @param yield The yield context to use for asynchronous operations. + */ + virtual void Flush(boost::asio::yield_context& yield) = 0; +}; + class String; class Value; From 60fafcb18d707e5a65f1ea123531ab810be266d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lorenz=20K=C3=A4stle?= <12514511+RincewindsHat@users.noreply.github.com> Date: Wed, 9 Jul 2025 15:24:11 +0200 Subject: [PATCH 374/415] Add plain fping CheckCommand to ITL --- doc/10-icinga-template-library.md | 25 +++++++++++++++++++++++++ itl/command-plugins.conf | 7 +++++++ 2 files changed, 32 insertions(+) diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md index 54b17b15e..003198cf0 100644 --- a/doc/10-icinga-template-library.md +++ b/doc/10-icinga-template-library.md @@ -510,6 +510,31 @@ Name | Description flexlm_licensefile | **Required.** Name of license file (usually license.dat). flexlm_timeout | **Optional.** Plugin time out in seconds. Defaults to 15. +### fping + +The [check_fping](https://www.monitoring-plugins.org/doc/man/check_fping.html) plugin +uses the `fping` command to ping the specified host for a fast check. Note that it is +necessary to set the `suid` flag on `fping`. + +This CheckCommand is agnostic on whether it receives a resolvable name, IPv6 address oder legacy IP address. + +Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): + +Name | Description +----------------|-------------- +fping_address | **Optional.** The host's IP address (v6 or v4). Defaults to "$address6$" or "$address$" (in that order). +fping_wrta | **Optional.** The RTA warning threshold in milliseconds. Defaults to 100. +fping_wpl | **Optional.** The packet loss warning threshold in %. Defaults to 5. +fping_crta | **Optional.** The RTA critical threshold in milliseconds. Defaults to 200. +fping_cpl | **Optional.** The packet loss critical threshold in %. Defaults to 15. +fping_number | **Optional.** The number of packets to send. Defaults to 5. +fping_interval | **Optional.** The interval between packets in milli-seconds. Defaults to 500. +fping_bytes | **Optional.** The size of ICMP packet. +fping_target_timeout | **Optional.** The target timeout in milli-seconds. +fping_source_ip | **Optional.** The name or ip address of the source ip. +fping_source_interface | **Optional.** The source interface name. +fping_extra_opts | **Optional.** Read extra plugin options from an ini file. + ### fping4 diff --git a/itl/command-plugins.conf b/itl/command-plugins.conf index ed8fa5b3c..6069e0995 100644 --- a/itl/command-plugins.conf +++ b/itl/command-plugins.conf @@ -151,6 +151,13 @@ template CheckCommand "fping-common" { vars.fping_interval = 500 } +object CheckCommand "fping" { + import "fping-common" + import "ipv4-or-ipv6" + + vars.fping_address = "$check_address$" +} + object CheckCommand "fping4" { import "fping-common" From be6844b40054b2b25863cd7a92379e83eb5ef309 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Thu, 10 Jul 2025 11:10:35 +0200 Subject: [PATCH 375/415] Fix typo --- doc/10-icinga-template-library.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md index 003198cf0..a266c40fd 100644 --- a/doc/10-icinga-template-library.md +++ b/doc/10-icinga-template-library.md @@ -516,7 +516,7 @@ The [check_fping](https://www.monitoring-plugins.org/doc/man/check_fping.html) p uses the `fping` command to ping the specified host for a fast check. Note that it is necessary to set the `suid` flag on `fping`. -This CheckCommand is agnostic on whether it receives a resolvable name, IPv6 address oder legacy IP address. +This CheckCommand is agnostic on whether it receives a resolvable name, IPv6 address or legacy IP address. Custom variables passed as [command parameters](03-monitoring-basics.md#command-passing-parameters): From 9dd2e2a3eca0d1d568bd587887ded9d63feff1f7 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 22 Apr 2025 09:45:29 +0200 Subject: [PATCH 376/415] Introduce `JsonEncoder` class --- lib/base/json.cpp | 592 ++++++++++++++++++++-------------------------- lib/base/json.hpp | 66 ++++++ 2 files changed, 320 insertions(+), 338 deletions(-) diff --git a/lib/base/json.cpp b/lib/base/json.cpp index 2d48970bb..4f03e692e 100644 --- a/lib/base/json.cpp +++ b/lib/base/json.cpp @@ -2,21 +2,264 @@ #include "base/json.hpp" #include "base/debug.hpp" -#include "base/namespace.hpp" #include "base/dictionary.hpp" -#include "base/array.hpp" +#include "base/namespace.hpp" #include "base/objectlock.hpp" -#include "base/convert.hpp" -#include "base/utility.hpp" -#include -#include -#include +#include #include #include #include using namespace icinga; +JsonEncoder::JsonEncoder(std::string& output, bool prettify) + : JsonEncoder{nlohmann::detail::output_adapter(output), prettify} +{ +} + +JsonEncoder::JsonEncoder(std::basic_ostream& stream, bool prettify) + : JsonEncoder{nlohmann::detail::output_adapter(stream), prettify} +{ +} + +JsonEncoder::JsonEncoder(nlohmann::detail::output_adapter_t w, bool prettify) + : m_Pretty(prettify), m_Writer(std::move(w)) +{ +} + +/** + * Encodes a single value into JSON and writes it to the underlying output stream. + * + * This method is the main entry point for encoding JSON data. It takes a value of any type that can + * be represented by our @c Value class recursively and encodes it into JSON in an efficient manner. + * If prettifying is enabled, the JSON output will be formatted with indentation and newlines for better + * readability, and the final JSON will also be terminated by a newline character. + * + * @param value The value to be JSON serialized. + */ +void JsonEncoder::Encode(const Value& value) +{ + switch (value.GetType()) { + case ValueEmpty: + Write("null"); + break; + case ValueBoolean: + Write(value.ToBool() ? "true" : "false"); + break; + case ValueString: + EncodeNlohmannJson(Utility::ValidateUTF8(value.Get())); + break; + case ValueNumber: + EncodeNumber(value.Get()); + break; + case ValueObject: { + const auto& obj = value.Get(); + const auto& type = obj->GetReflectionType(); + if (type == Namespace::TypeInstance) { + static constexpr auto extractor = [](const NamespaceValue& v) -> const Value& { return v.Val; }; + EncodeObject(static_pointer_cast(obj), extractor); + } else if (type == Dictionary::TypeInstance) { + static constexpr auto extractor = [](const Value& v) -> const Value& { return v; }; + EncodeObject(static_pointer_cast(obj), extractor); + } else if (type == Array::TypeInstance) { + EncodeArray(static_pointer_cast(obj)); + } else if (auto gen(dynamic_pointer_cast(obj)); gen) { + EncodeValueGenerator(gen); + } else { + // Some other non-serializable object type! + EncodeNlohmannJson(Utility::ValidateUTF8(obj->ToString())); + } + break; + } + default: + VERIFY(!"Invalid variant type."); + } + + // If we are at the top level of the JSON object and prettifying is enabled, we need to end + // the JSON with a newline character to ensure that the output is properly formatted. + if (m_Indent == 0 && m_Pretty) { + Write("\n"); + } +} + +/** + * Encodes an Array object into JSON and writes it to the output stream. + * + * @param array The Array object to be serialized into JSON. + */ +void JsonEncoder::EncodeArray(const Array::Ptr& array) +{ + BeginContainer('['); + ObjectLock olock(array); + bool isEmpty = true; + for (const auto& item : array) { + WriteSeparatorAndIndentStrIfNeeded(!isEmpty); + isEmpty = false; + Encode(item); + } + EndContainer(']', isEmpty); +} + +/** + * Encodes a ValueGenerator object into JSON and writes it to the output stream. + * + * This will iterate through the generator, encoding each value it produces until it is exhausted. + * + * @param generator The ValueGenerator object to be serialized into JSON. + */ +void JsonEncoder::EncodeValueGenerator(const ValueGenerator::Ptr& generator) +{ + BeginContainer('['); + bool isEmpty = true; + while (auto result = generator->Next()) { + WriteSeparatorAndIndentStrIfNeeded(!isEmpty); + isEmpty = false; + Encode(*result); + } + EndContainer(']', isEmpty); +} + +/** + * Encodes an Icinga 2 object (Namespace or Dictionary) into JSON and writes it to @c m_Writer. + * + * @tparam Iterable Type of the container (Namespace or Dictionary). + * @tparam ValExtractor Type of the value extractor function used to extract values from the container's iterator. + * + * @param container The container to JSON serialize. + * @param extractor The value extractor function used to extract values from the container's iterator. + */ +template +void JsonEncoder::EncodeObject(const Iterable& container, const ValExtractor& extractor) +{ + static_assert(std::is_same_v || std::is_same_v, + "Container must be a Namespace or Dictionary"); + + BeginContainer('{'); + ObjectLock olock(container); + bool isEmpty = true; + for (const auto& [key, val] : container) { + WriteSeparatorAndIndentStrIfNeeded(!isEmpty); + isEmpty = false; + + EncodeNlohmannJson(Utility::ValidateUTF8(key)); + Write(m_Pretty ? ": " : ":"); + Encode(extractor(val)); + } + EndContainer('}', isEmpty); +} + +/** + * Dumps a nlohmann::json object to the output stream using the serializer. + * + * This function uses the @c nlohmann::detail::serializer to dump the provided @c nlohmann::json + * object to the output stream managed by the @c JsonEncoder. + * + * @param json The nlohmann::json object to encode. + */ +void JsonEncoder::EncodeNlohmannJson(const nlohmann::json& json) const +{ + nlohmann::detail::serializer s(m_Writer, ' ', nlohmann::json::error_handler_t::strict); + s.dump(json, m_Pretty, true, 0, 0); +} + +/** + * Encodes a double value into JSON format and writes it to the output stream. + * + * This function checks if the double value can be safely cast to an integer or unsigned integer type + * without loss of precision. If it can, it will serialize it as such; otherwise, it will serialize + * it as a double. This is particularly useful for ensuring that values like 0.0 are serialized as 0, + * which can be important for compatibility with clients like Icinga DB that expect integers in such cases. + * + * @param value The double value to encode as JSON. + */ +void JsonEncoder::EncodeNumber(double value) const +{ + try { + if (value < 0) { + if (auto ll(boost::numeric_cast(value)); ll == value) { + EncodeNlohmannJson(ll); + return; + } + } else if (auto ull(boost::numeric_cast(value)); ull == value) { + EncodeNlohmannJson(ull); + return; + } + // If we reach this point, the value cannot be safely cast to a signed or unsigned integer + // type because it would otherwise lose its precision. If the value was just too large to fit + // into the above types, then boost will throw an exception and end up in the below catch block. + // So, in either case, serialize the number as-is without any casting. + } catch (const boost::bad_numeric_cast&) {} + + EncodeNlohmannJson(value); +} + +/** + * Writes a string to the underlying output stream. + * + * This function writes the provided string view directly to the output stream without any additional formatting. + * + * @param sv The string view to write to the output stream. + */ +void JsonEncoder::Write(const std::string_view& sv) const +{ + m_Writer->write_characters(sv.data(), sv.size()); +} + +/** + * Begins a JSON container (object or array) by writing the opening character and adjusting the + * indentation level if pretty-printing is enabled. + * + * @param openChar The character that opens the container (either '{' for objects or '[' for arrays). + */ +void JsonEncoder::BeginContainer(char openChar) +{ + if (m_Pretty) { + m_Indent += m_IndentSize; + if (m_IndentStr.size() < m_Indent) { + m_IndentStr.resize(m_IndentStr.size() * 2, ' '); + } + } + m_Writer->write_character(openChar); +} + +/** + * Ends a JSON container (object or array) by writing the closing character and adjusting the + * indentation level if pretty-printing is enabled. + * + * @param closeChar The character that closes the container (either '}' for objects or ']' for arrays). + * @param isContainerEmpty Whether the container is empty, used to determine if a newline should be written. + */ +void JsonEncoder::EndContainer(char closeChar, bool isContainerEmpty) +{ + if (m_Pretty) { + ASSERT(m_Indent >= m_IndentSize); // Ensure we don't underflow the indent size. + m_Indent -= m_IndentSize; + if (!isContainerEmpty) { + Write("\n"); + m_Writer->write_characters(m_IndentStr.c_str(), m_Indent); + } + } + m_Writer->write_character(closeChar); +} + +/** + * Writes a separator (comma) and an indentation string if pretty-printing is enabled. + * + * This function is used to separate items in a JSON array or object and to maintain the correct indentation level. + * + * @param emitComma Whether to emit a comma. This is typically true for all but the first item in a container. + */ +void JsonEncoder::WriteSeparatorAndIndentStrIfNeeded(bool emitComma) const +{ + if (emitComma) { + Write(","); + } + if (m_Pretty) { + Write("\n"); + m_Writer->write_characters(m_IndentStr.c_str(), m_Indent); + } +} + class JsonSax : public nlohmann::json_sax { public: @@ -44,165 +287,12 @@ private: void FillCurrentTarget(Value value); }; -const char l_Null[] = "null"; -const char l_False[] = "false"; -const char l_True[] = "true"; -const char l_Indent[] = " "; - -// https://github.com/nlohmann/json/issues/1512 -template -class JsonEncoder -{ -public: - void Null(); - void Boolean(bool value); - void NumberFloat(double value); - void Strng(String value); - void StartObject(); - void Key(String value); - void EndObject(); - void StartArray(); - void EndArray(); - - String GetResult(); - -private: - std::vector m_Result; - String m_CurrentKey; - std::stack> m_CurrentSubtree; - - void AppendChar(char c); - - template - void AppendChars(Iterator begin, Iterator end); - - void AppendJson(nlohmann::json json); - - void BeforeItem(); - - void FinishContainer(char terminator); -}; - -template -void Encode(JsonEncoder& stateMachine, const Value& value); - -template -inline -void EncodeNamespace(JsonEncoder& stateMachine, const Namespace::Ptr& ns) -{ - stateMachine.StartObject(); - - ObjectLock olock(ns); - for (const Namespace::Pair& kv : ns) { - stateMachine.Key(Utility::ValidateUTF8(kv.first)); - Encode(stateMachine, kv.second.Val); - } - - stateMachine.EndObject(); -} - -template -inline -void EncodeDictionary(JsonEncoder& stateMachine, const Dictionary::Ptr& dict) -{ - stateMachine.StartObject(); - - ObjectLock olock(dict); - for (const Dictionary::Pair& kv : dict) { - stateMachine.Key(Utility::ValidateUTF8(kv.first)); - Encode(stateMachine, kv.second); - } - - stateMachine.EndObject(); -} - -template -inline -void EncodeArray(JsonEncoder& stateMachine, const Array::Ptr& arr) -{ - stateMachine.StartArray(); - - ObjectLock olock(arr); - for (const Value& value : arr) { - Encode(stateMachine, value); - } - - stateMachine.EndArray(); -} - -template -void Encode(JsonEncoder& stateMachine, const Value& value) -{ - switch (value.GetType()) { - case ValueNumber: - stateMachine.NumberFloat(value.Get()); - break; - - case ValueBoolean: - stateMachine.Boolean(value.ToBool()); - break; - - case ValueString: - stateMachine.Strng(Utility::ValidateUTF8(value.Get())); - break; - - case ValueObject: - { - const Object::Ptr& obj = value.Get(); - - { - Namespace::Ptr ns = dynamic_pointer_cast(obj); - if (ns) { - EncodeNamespace(stateMachine, ns); - break; - } - } - - { - Dictionary::Ptr dict = dynamic_pointer_cast(obj); - if (dict) { - EncodeDictionary(stateMachine, dict); - break; - } - } - - { - Array::Ptr arr = dynamic_pointer_cast(obj); - if (arr) { - EncodeArray(stateMachine, arr); - break; - } - } - - // obj is most likely a function => "Object of type 'Function'" - Encode(stateMachine, obj->ToString()); - break; - } - - case ValueEmpty: - stateMachine.Null(); - break; - - default: - VERIFY(!"Invalid variant type."); - } -} - String icinga::JsonEncode(const Value& value, bool pretty_print) { - if (pretty_print) { - JsonEncoder stateMachine; - - Encode(stateMachine, value); - - return stateMachine.GetResult() + "\n"; - } else { - JsonEncoder stateMachine; - - Encode(stateMachine, value); - - return stateMachine.GetResult(); - } + std::string output; + JsonEncoder encoder(output, pretty_print); + encoder.Encode(value); + return String(std::move(output)); } Value icinga::JsonDecode(const String& data) @@ -348,177 +438,3 @@ void JsonSax::FillCurrentTarget(Value value) } } } - -template -inline -void JsonEncoder::Null() -{ - BeforeItem(); - AppendChars((const char*)l_Null, (const char*)l_Null + 4); -} - -template -inline -void JsonEncoder::Boolean(bool value) -{ - BeforeItem(); - - if (value) { - AppendChars((const char*)l_True, (const char*)l_True + 4); - } else { - AppendChars((const char*)l_False, (const char*)l_False + 5); - } -} - -template -inline -void JsonEncoder::NumberFloat(double value) -{ - BeforeItem(); - - // Make sure 0.0 is serialized as 0, so e.g. Icinga DB can parse it as int. - if (value < 0) { - long long i = value; - - if (i == value) { - AppendJson(i); - } else { - AppendJson(value); - } - } else { - unsigned long long i = value; - - if (i == value) { - AppendJson(i); - } else { - AppendJson(value); - } - } -} - -template -inline -void JsonEncoder::Strng(String value) -{ - BeforeItem(); - AppendJson(std::move(value)); -} - -template -inline -void JsonEncoder::StartObject() -{ - BeforeItem(); - AppendChar('{'); - - m_CurrentSubtree.push(2); -} - -template -inline -void JsonEncoder::Key(String value) -{ - m_CurrentKey = std::move(value); -} - -template -inline -void JsonEncoder::EndObject() -{ - FinishContainer('}'); -} - -template -inline -void JsonEncoder::StartArray() -{ - BeforeItem(); - AppendChar('['); - - m_CurrentSubtree.push(0); -} - -template -inline -void JsonEncoder::EndArray() -{ - FinishContainer(']'); -} - -template -inline -String JsonEncoder::GetResult() -{ - return String(m_Result.begin(), m_Result.end()); -} - -template -inline -void JsonEncoder::AppendChar(char c) -{ - m_Result.emplace_back(c); -} - -template -template -inline -void JsonEncoder::AppendChars(Iterator begin, Iterator end) -{ - m_Result.insert(m_Result.end(), begin, end); -} - -template -inline -void JsonEncoder::AppendJson(nlohmann::json json) -{ - nlohmann::detail::serializer(nlohmann::detail::output_adapter(m_Result), ' ').dump(std::move(json), prettyPrint, true, 0); -} - -template -inline -void JsonEncoder::BeforeItem() -{ - if (!m_CurrentSubtree.empty()) { - auto& node (m_CurrentSubtree.top()); - - if (node[0]) { - AppendChar(','); - } else { - node[0] = true; - } - - if (prettyPrint) { - AppendChar('\n'); - - for (auto i (m_CurrentSubtree.size()); i; --i) { - AppendChars((const char*)l_Indent, (const char*)l_Indent + 4); - } - } - - if (node[1]) { - AppendJson(std::move(m_CurrentKey)); - AppendChar(':'); - - if (prettyPrint) { - AppendChar(' '); - } - } - } -} - -template -inline -void JsonEncoder::FinishContainer(char terminator) -{ - if (prettyPrint && m_CurrentSubtree.top()[0]) { - AppendChar('\n'); - - for (auto i (m_CurrentSubtree.size() - 1u); i; --i) { - AppendChars((const char*)l_Indent, (const char*)l_Indent + 4); - } - } - - AppendChar(terminator); - - m_CurrentSubtree.pop(); -} diff --git a/lib/base/json.hpp b/lib/base/json.hpp index 3df6e797e..984a8d1d4 100644 --- a/lib/base/json.hpp +++ b/lib/base/json.hpp @@ -4,6 +4,9 @@ #define JSON_H #include "base/i2-base.hpp" +#include "base/array.hpp" +#include "base/generator.hpp" +#include "base/utility.hpp" #include #include @@ -41,6 +44,69 @@ public: class String; class Value; +/** + * JSON encoder. + * + * This class can be used to encode Icinga Value types into JSON format and write them to an output stream. + * The supported stream types include any @c std::ostream like objects and our own @c AsyncJsonWriter, which + * allows writing JSON data to an Asio stream asynchronously. The nlohmann/json library already provides + * full support for the former stream type, while the latter is fully implemented by our own and satisfies the + * @c nlohmann::detail::output_adapter_protocol<> interface as well. Therefore, any concrete implementation of + * @c AsyncJsonWriter may be used to write the produced JSON directly to an Asio either TCP or TLS stream without + * any additional buffering other than the one used by the Asio buffered_stream<> class internally. + * + * The JSON encoder generates most of the low level JSON tokens, but it still relies on the already existing + * @c nlohmann::detail::serializer<> class to dump numbers and ASCII validated JSON strings. This means that the + * encoder doesn't perform any kind of JSON validation or escaping on its own, but simply delegates all this kind + * of work to serializer<>. However, Strings are UTF-8 validated beforehand using the @c Utility::ValidateUTF8() + * function and only the validated (copy of the original) String is passed to the serializer. + * + * The generated JSON can be either prettified or compact, depending on your needs. The prettified JSON object + * is indented with 4 spaces and grows linearly with the depth of the object tree. + * + * @ingroup base + */ +class JsonEncoder +{ +public: + explicit JsonEncoder(std::string& output, bool prettify = false); + explicit JsonEncoder(std::basic_ostream& stream, bool prettify = false); + explicit JsonEncoder(nlohmann::detail::output_adapter_t w, bool prettify = false); + + void Encode(const Value& value); + +private: + void EncodeArray(const Array::Ptr& array); + void EncodeValueGenerator(const ValueGenerator::Ptr& generator); + + template + void EncodeObject(const Iterable& container, const ValExtractor& extractor); + + void EncodeNlohmannJson(const nlohmann::json& json) const; + void EncodeNumber(double value) const; + + void Write(const std::string_view& sv) const; + void BeginContainer(char openChar); + void EndContainer(char closeChar, bool isContainerEmpty = false); + void WriteSeparatorAndIndentStrIfNeeded(bool emitComma) const; + + // The number of spaces to use for indentation in prettified JSON. + static constexpr uint8_t m_IndentSize = 4; + + bool m_Pretty; // Whether to pretty-print the JSON output. + unsigned m_Indent{0}; // The current indentation level for pretty-printing. + /** + * Pre-allocate for 8 levels of indentation for pretty-printing. + * + * This is used to avoid reallocating the string on every indent level change. + * The size of this string is dynamically adjusted if the indentation level exceeds its initial size at some point. + */ + std::string m_IndentStr{8*m_IndentSize, ' '}; + + // The output stream adapter for writing JSON data. This can be either a std::ostream or an Asio stream adapter. + nlohmann::detail::output_adapter_t m_Writer; +}; + String JsonEncode(const Value& value, bool pretty_print = false); Value JsonDecode(const String& data); From 2461e0415df6ea4b61105fe23c21e24b3c3cc284 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 22 Apr 2025 09:48:09 +0200 Subject: [PATCH 377/415] Introduce `JsonEncode` helper function It's just a wrapper around the `JsonEncoder` class to simplify its usage. --- lib/base/json.cpp | 17 +++++++++++++++-- lib/base/json.hpp | 3 ++- test/base-json.cpp | 39 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 53 insertions(+), 6 deletions(-) diff --git a/lib/base/json.cpp b/lib/base/json.cpp index 4f03e692e..74bc9f63a 100644 --- a/lib/base/json.cpp +++ b/lib/base/json.cpp @@ -287,14 +287,27 @@ private: void FillCurrentTarget(Value value); }; -String icinga::JsonEncode(const Value& value, bool pretty_print) +String icinga::JsonEncode(const Value& value, bool prettify) { std::string output; - JsonEncoder encoder(output, pretty_print); + JsonEncoder encoder(output, prettify); encoder.Encode(value); return String(std::move(output)); } +/** + * Serializes an Icinga Value into a JSON object and writes it to the given output stream. + * + * @param value The value to be JSON serialized. + * @param os The output stream to write the JSON data to. + * @param prettify Whether to pretty print the serialized JSON. + */ +void icinga::JsonEncode(const Value& value, std::ostream& os, bool prettify) +{ + JsonEncoder encoder(os, prettify); + encoder.Encode(value); +} + Value icinga::JsonDecode(const String& data) { String sanitized (Utility::ValidateUTF8(data)); diff --git a/lib/base/json.hpp b/lib/base/json.hpp index 984a8d1d4..63828316c 100644 --- a/lib/base/json.hpp +++ b/lib/base/json.hpp @@ -107,7 +107,8 @@ private: nlohmann::detail::output_adapter_t m_Writer; }; -String JsonEncode(const Value& value, bool pretty_print = false); +String JsonEncode(const Value& value, bool prettify = false); +void JsonEncode(const Value& value, std::ostream& os, bool prettify = false); Value JsonDecode(const String& data); } diff --git a/test/base-json.cpp b/test/base-json.cpp index 02bbebb6d..8df282c89 100644 --- a/test/base-json.cpp +++ b/test/base-json.cpp @@ -4,10 +4,13 @@ #include "base/function.hpp" #include "base/namespace.hpp" #include "base/array.hpp" +#include "base/generator.hpp" #include "base/objectlock.hpp" #include "base/json.hpp" #include #include +#include +#include using namespace icinga; @@ -15,26 +18,51 @@ BOOST_AUTO_TEST_SUITE(base_json) BOOST_AUTO_TEST_CASE(encode) { + auto generate = []() -> std::optional { + static int count = 0; + if (++count == 4) { + count = 0; + return std::nullopt; + } + return Value(count); + }; + Dictionary::Ptr input (new Dictionary({ { "array", new Array({ new Namespace() }) }, { "false", false }, - { "float", -1.25 }, + // Use double max value to test JSON encoding of large numbers and trigger boost numeric_cast exceptions + { "max_double", std::numeric_limits::max() }, + // Test the maximum number that can be exact represented by a double is 2^64-2048. + { "max_int_in_double", std::nextafter(std::pow(2, 64), 0.0) }, + { "float", -1.25f }, + { "float_without_fraction", 23.0f }, { "fx", new Function("", []() {}) }, { "int", -42 }, { "null", Value() }, { "string", "LF\nTAB\tAUml\xC3\xA4Ill\xC3" }, { "true", true }, - { "uint", 23u } + { "uint", 23u }, + { "generator", new ValueGenerator(generate) }, + { "empty_generator", new ValueGenerator([]() -> std::optional { return std::nullopt; }) }, })); String output (R"EOF({ "array": [ {} ], + "empty_generator": [], "false": false, "float": -1.25, + "float_without_fraction": 23, "fx": "Object of type 'Function'", + "generator": [ + 1, + 2, + 3 + ], "int": -42, + "max_double": 1.7976931348623157e+308, + "max_int_in_double": 18446744073709549568, "null": null, "string": "LF\nTAB\tAUml\u00e4Ill\ufffd", "true": true, @@ -42,7 +70,12 @@ BOOST_AUTO_TEST_CASE(encode) } )EOF"); - BOOST_CHECK(JsonEncode(input, true) == output); + auto got(JsonEncode(input, true)); + BOOST_CHECK_EQUAL(output, got); + + std::ostringstream oss; + JsonEncode(input, oss, true); + BOOST_CHECK_EQUAL(output, oss.str()); boost::algorithm::replace_all(output, " ", ""); boost::algorithm::replace_all(output, "Objectoftype'Function'", "Object of type 'Function'"); From 57726fbb663fb6ee884a547bc997decc24a85787 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 3 Jul 2025 12:09:16 +0200 Subject: [PATCH 378/415] Do not require olock on frozen `Namespace`, `Dictionary` & `Array` --- lib/base/array.cpp | 11 ++++++++--- lib/base/array.hpp | 4 +++- lib/base/dictionary.cpp | 11 ++++++++--- lib/base/dictionary.hpp | 4 +++- lib/base/namespace.cpp | 11 ++++++++--- lib/base/namespace.hpp | 1 + 6 files changed, 31 insertions(+), 11 deletions(-) diff --git a/lib/base/array.cpp b/lib/base/array.cpp index 73adcb279..fe95f5d88 100644 --- a/lib/base/array.cpp +++ b/lib/base/array.cpp @@ -96,7 +96,7 @@ void Array::Add(Value value) */ Array::Iterator Array::Begin() { - ASSERT(OwnsLock()); + ASSERT(Frozen() || OwnsLock()); return m_Data.begin(); } @@ -110,7 +110,7 @@ Array::Iterator Array::Begin() */ Array::Iterator Array::End() { - ASSERT(OwnsLock()); + ASSERT(Frozen() || OwnsLock()); return m_Data.end(); } @@ -327,7 +327,12 @@ Array::Ptr Array::Unique() const void Array::Freeze() { ObjectLock olock(this); - m_Frozen = true; + m_Frozen.store(true, std::memory_order_release); +} + +bool Array::Frozen() const +{ + return m_Frozen.load(std::memory_order_acquire); } Value Array::GetFieldByName(const String& field, bool sandboxed, const DebugInfo& debugInfo) const diff --git a/lib/base/array.hpp b/lib/base/array.hpp index 9589d1901..fb8257c17 100644 --- a/lib/base/array.hpp +++ b/lib/base/array.hpp @@ -4,6 +4,7 @@ #define ARRAY_H #include "base/i2-base.hpp" +#include "base/atomic.hpp" #include "base/objectlock.hpp" #include "base/value.hpp" #include @@ -98,13 +99,14 @@ public: Array::Ptr Unique() const; void Freeze(); + bool Frozen() const; Value GetFieldByName(const String& field, bool sandboxed, const DebugInfo& debugInfo) const override; void SetFieldByName(const String& field, const Value& value, const DebugInfo& debugInfo) override; private: std::vector m_Data; /**< The data for the array. */ - bool m_Frozen{false}; + Atomic m_Frozen{false}; }; Array::Iterator begin(const Array::Ptr& x); diff --git a/lib/base/dictionary.cpp b/lib/base/dictionary.cpp index 89b6c4cbc..c45bee3d8 100644 --- a/lib/base/dictionary.cpp +++ b/lib/base/dictionary.cpp @@ -132,7 +132,7 @@ bool Dictionary::Contains(const String& key) const */ Dictionary::Iterator Dictionary::Begin() { - ASSERT(OwnsLock()); + ASSERT(Frozen() || OwnsLock()); return m_Data.begin(); } @@ -146,7 +146,7 @@ Dictionary::Iterator Dictionary::Begin() */ Dictionary::Iterator Dictionary::End() { - ASSERT(OwnsLock()); + ASSERT(Frozen() || OwnsLock()); return m_Data.end(); } @@ -276,7 +276,12 @@ String Dictionary::ToString() const void Dictionary::Freeze() { ObjectLock olock(this); - m_Frozen = true; + m_Frozen.store(true, std::memory_order_release); +} + +bool Dictionary::Frozen() const +{ + return m_Frozen.load(std::memory_order_acquire); } Value Dictionary::GetFieldByName(const String& field, bool, const DebugInfo& debugInfo) const diff --git a/lib/base/dictionary.hpp b/lib/base/dictionary.hpp index ba2fbe82c..f95bcc699 100644 --- a/lib/base/dictionary.hpp +++ b/lib/base/dictionary.hpp @@ -4,6 +4,7 @@ #define DICTIONARY_H #include "base/i2-base.hpp" +#include "base/atomic.hpp" #include "base/object.hpp" #include "base/value.hpp" #include @@ -69,6 +70,7 @@ public: String ToString() const override; void Freeze(); + bool Frozen() const; Value GetFieldByName(const String& field, bool sandboxed, const DebugInfo& debugInfo) const override; void SetFieldByName(const String& field, const Value& value, const DebugInfo& debugInfo) override; @@ -78,7 +80,7 @@ public: private: std::map m_Data; /**< The data for the dictionary. */ mutable std::shared_timed_mutex m_DataMutex; - bool m_Frozen{false}; + Atomic m_Frozen{false}; }; Dictionary::Iterator begin(const Dictionary::Ptr& x); diff --git a/lib/base/namespace.cpp b/lib/base/namespace.cpp index 1f53efc92..85ed7fa1d 100644 --- a/lib/base/namespace.cpp +++ b/lib/base/namespace.cpp @@ -119,7 +119,12 @@ void Namespace::Remove(const String& field) void Namespace::Freeze() { ObjectLock olock(this); - m_Frozen = true; + m_Frozen.store(true, std::memory_order_release); +} + +bool Namespace::Frozen() const +{ + return m_Frozen.load(std::memory_order_acquire); } std::shared_lock Namespace::ReadLockUnlessFrozen() const @@ -160,14 +165,14 @@ bool Namespace::GetOwnField(const String& field, Value *result) const Namespace::Iterator Namespace::Begin() { - ASSERT(OwnsLock()); + ASSERT(Frozen() || OwnsLock()); return m_Data.begin(); } Namespace::Iterator Namespace::End() { - ASSERT(OwnsLock()); + ASSERT(Frozen() || OwnsLock()); return m_Data.end(); } diff --git a/lib/base/namespace.hpp b/lib/base/namespace.hpp index 1a028e2c5..c7f1a7e0a 100644 --- a/lib/base/namespace.hpp +++ b/lib/base/namespace.hpp @@ -73,6 +73,7 @@ public: bool Contains(const String& field) const; void Remove(const String& field); void Freeze(); + bool Frozen() const; Iterator Begin(); Iterator End(); From 398b5e3193b9d5f8f93824602a28595afb332939 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 8 Jul 2025 17:27:34 +0200 Subject: [PATCH 379/415] Implement `LockIfRequired()` method for `Namespace`, `Dictionary` & `Array` --- lib/base/array.cpp | 14 ++++++++++++++ lib/base/array.hpp | 1 + lib/base/dictionary.cpp | 15 ++++++++++++++- lib/base/dictionary.hpp | 2 ++ lib/base/namespace.cpp | 15 ++++++++++++++- lib/base/namespace.hpp | 2 ++ 6 files changed, 47 insertions(+), 2 deletions(-) diff --git a/lib/base/array.cpp b/lib/base/array.cpp index fe95f5d88..9906d6496 100644 --- a/lib/base/array.cpp +++ b/lib/base/array.cpp @@ -335,6 +335,20 @@ bool Array::Frozen() const return m_Frozen.load(std::memory_order_acquire); } +/** + * Returns an already locked ObjectLock if the array is frozen. + * Otherwise, returns an unlocked object lock. + * + * @returns An object lock. + */ +ObjectLock Array::LockIfRequired() +{ + if (Frozen()) { + return ObjectLock(this, std::defer_lock); + } + return ObjectLock(this); +} + Value Array::GetFieldByName(const String& field, bool sandboxed, const DebugInfo& debugInfo) const { int index; diff --git a/lib/base/array.hpp b/lib/base/array.hpp index fb8257c17..f8f923494 100644 --- a/lib/base/array.hpp +++ b/lib/base/array.hpp @@ -100,6 +100,7 @@ public: Array::Ptr Unique() const; void Freeze(); bool Frozen() const; + ObjectLock LockIfRequired(); Value GetFieldByName(const String& field, bool sandboxed, const DebugInfo& debugInfo) const override; void SetFieldByName(const String& field, const Value& value, const DebugInfo& debugInfo) override; diff --git a/lib/base/dictionary.cpp b/lib/base/dictionary.cpp index c45bee3d8..2599a1039 100644 --- a/lib/base/dictionary.cpp +++ b/lib/base/dictionary.cpp @@ -1,7 +1,6 @@ /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ #include "base/dictionary.hpp" -#include "base/objectlock.hpp" #include "base/debug.hpp" #include "base/primitivetype.hpp" #include "base/configwriter.hpp" @@ -284,6 +283,20 @@ bool Dictionary::Frozen() const return m_Frozen.load(std::memory_order_acquire); } +/** + * Returns an already locked ObjectLock if the dictionary is frozen. + * Otherwise, returns an unlocked object lock. + * + * @returns An object lock. + */ +ObjectLock Dictionary::LockIfRequired() +{ + if (Frozen()) { + return ObjectLock(this, std::defer_lock); + } + return ObjectLock(this); +} + Value Dictionary::GetFieldByName(const String& field, bool, const DebugInfo& debugInfo) const { Value value; diff --git a/lib/base/dictionary.hpp b/lib/base/dictionary.hpp index f95bcc699..f0bbf1b46 100644 --- a/lib/base/dictionary.hpp +++ b/lib/base/dictionary.hpp @@ -6,6 +6,7 @@ #include "base/i2-base.hpp" #include "base/atomic.hpp" #include "base/object.hpp" +#include "base/objectlock.hpp" #include "base/value.hpp" #include #include @@ -71,6 +72,7 @@ public: void Freeze(); bool Frozen() const; + ObjectLock LockIfRequired(); Value GetFieldByName(const String& field, bool sandboxed, const DebugInfo& debugInfo) const override; void SetFieldByName(const String& field, const Value& value, const DebugInfo& debugInfo) override; diff --git a/lib/base/namespace.cpp b/lib/base/namespace.cpp index 85ed7fa1d..383b2ecbd 100644 --- a/lib/base/namespace.cpp +++ b/lib/base/namespace.cpp @@ -1,7 +1,6 @@ /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ #include "base/namespace.hpp" -#include "base/objectlock.hpp" #include "base/debug.hpp" #include "base/primitivetype.hpp" #include "base/debuginfo.hpp" @@ -127,6 +126,20 @@ bool Namespace::Frozen() const return m_Frozen.load(std::memory_order_acquire); } +/** + * Returns an already locked ObjectLock if the namespace is frozen. + * Otherwise, returns an unlocked object lock. + * + * @returns An object lock. + */ +ObjectLock Namespace::LockIfRequired() +{ + if (Frozen()) { + return ObjectLock(this, std::defer_lock); + } + return ObjectLock(this); +} + std::shared_lock Namespace::ReadLockUnlessFrozen() const { if (m_Frozen.load(std::memory_order_relaxed)) { diff --git a/lib/base/namespace.hpp b/lib/base/namespace.hpp index c7f1a7e0a..194671099 100644 --- a/lib/base/namespace.hpp +++ b/lib/base/namespace.hpp @@ -5,6 +5,7 @@ #include "base/i2-base.hpp" #include "base/object.hpp" +#include "base/objectlock.hpp" #include "base/shared-object.hpp" #include "base/value.hpp" #include "base/debuginfo.hpp" @@ -74,6 +75,7 @@ public: void Remove(const String& field); void Freeze(); bool Frozen() const; + ObjectLock LockIfRequired(); Iterator Begin(); Iterator End(); From dad4c0889f753781b64d67466bdef0d2c1f1e00d Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Thu, 3 Jul 2025 18:02:30 +0200 Subject: [PATCH 380/415] JsonEncoder: lock olock conditionally & flush output regularly --- lib/base/json.cpp | 78 ++++++++++++++++++++++++++++++++++++++--------- lib/base/json.hpp | 11 ++++--- 2 files changed, 71 insertions(+), 18 deletions(-) diff --git a/lib/base/json.cpp b/lib/base/json.cpp index 74bc9f63a..e2749bbde 100644 --- a/lib/base/json.cpp +++ b/lib/base/json.cpp @@ -23,7 +23,7 @@ JsonEncoder::JsonEncoder(std::basic_ostream& stream, bool prettify) } JsonEncoder::JsonEncoder(nlohmann::detail::output_adapter_t w, bool prettify) - : m_Pretty(prettify), m_Writer(std::move(w)) + : m_IsAsyncWriter{dynamic_cast(w.get()) != nullptr}, m_Pretty(prettify), m_Writer(std::move(w)) { } @@ -35,9 +35,18 @@ JsonEncoder::JsonEncoder(nlohmann::detail::output_adapter_t w, bool pretti * If prettifying is enabled, the JSON output will be formatted with indentation and newlines for better * readability, and the final JSON will also be terminated by a newline character. * + * @note If the used output adapter performs asynchronous I/O operations (it's derived from @c AsyncJsonWriter), + * please provide a @c boost::asio::yield_context object to allow the encoder to flush the output stream in a + * safe manner. The encoder will try to regularly give the output stream a chance to flush its data when it is + * safe to do so, but for this to work, there must be a valid yield context provided. Otherwise, the encoder + * will not attempt to flush the output stream at all, which may lead to huge memory consumption when encoding + * large JSON objects or arrays. + * * @param value The value to be JSON serialized. + * @param yc The optional yield context for asynchronous operations. If provided, it allows the encoder + * to flush the output stream safely when it has not acquired any object lock on the parent containers. */ -void JsonEncoder::Encode(const Value& value) +void JsonEncoder::Encode(const Value& value, boost::asio::yield_context* yc) { switch (value.GetType()) { case ValueEmpty: @@ -57,14 +66,14 @@ void JsonEncoder::Encode(const Value& value) const auto& type = obj->GetReflectionType(); if (type == Namespace::TypeInstance) { static constexpr auto extractor = [](const NamespaceValue& v) -> const Value& { return v.Val; }; - EncodeObject(static_pointer_cast(obj), extractor); + EncodeObject(static_pointer_cast(obj), extractor, yc); } else if (type == Dictionary::TypeInstance) { static constexpr auto extractor = [](const Value& v) -> const Value& { return v; }; - EncodeObject(static_pointer_cast(obj), extractor); + EncodeObject(static_pointer_cast(obj), extractor, yc); } else if (type == Array::TypeInstance) { - EncodeArray(static_pointer_cast(obj)); + EncodeArray(static_pointer_cast(obj), yc); } else if (auto gen(dynamic_pointer_cast(obj)); gen) { - EncodeValueGenerator(gen); + EncodeValueGenerator(gen, yc); } else { // Some other non-serializable object type! EncodeNlohmannJson(Utility::ValidateUTF8(obj->ToString())); @@ -86,16 +95,23 @@ void JsonEncoder::Encode(const Value& value) * Encodes an Array object into JSON and writes it to the output stream. * * @param array The Array object to be serialized into JSON. + * @param yc The optional yield context for asynchronous operations. If provided, it allows the encoder + * to flush the output stream safely when it has not acquired any object lock. */ -void JsonEncoder::EncodeArray(const Array::Ptr& array) +void JsonEncoder::EncodeArray(const Array::Ptr& array, boost::asio::yield_context* yc) { BeginContainer('['); - ObjectLock olock(array); + auto olock = array->LockIfRequired(); + if (olock) { + yc = nullptr; // We've acquired an object lock, never allow asynchronous operations. + } + bool isEmpty = true; for (const auto& item : array) { WriteSeparatorAndIndentStrIfNeeded(!isEmpty); isEmpty = false; - Encode(item); + Encode(item, yc); + FlushIfSafe(yc); } EndContainer(']', isEmpty); } @@ -106,15 +122,18 @@ void JsonEncoder::EncodeArray(const Array::Ptr& array) * This will iterate through the generator, encoding each value it produces until it is exhausted. * * @param generator The ValueGenerator object to be serialized into JSON. + * @param yc The optional yield context for asynchronous operations. If provided, it allows the encoder + * to flush the output stream safely when it has not acquired any object lock on the parent containers. */ -void JsonEncoder::EncodeValueGenerator(const ValueGenerator::Ptr& generator) +void JsonEncoder::EncodeValueGenerator(const ValueGenerator::Ptr& generator, boost::asio::yield_context* yc) { BeginContainer('['); bool isEmpty = true; while (auto result = generator->Next()) { WriteSeparatorAndIndentStrIfNeeded(!isEmpty); isEmpty = false; - Encode(*result); + Encode(*result, yc); + FlushIfSafe(yc); } EndContainer(']', isEmpty); } @@ -127,15 +146,21 @@ void JsonEncoder::EncodeValueGenerator(const ValueGenerator::Ptr& generator) * * @param container The container to JSON serialize. * @param extractor The value extractor function used to extract values from the container's iterator. + * @param yc The optional yield context for asynchronous operations. It will only be set when the encoder + * has not acquired any object lock on the parent containers, allowing safe asynchronous operations. */ template -void JsonEncoder::EncodeObject(const Iterable& container, const ValExtractor& extractor) +void JsonEncoder::EncodeObject(const Iterable& container, const ValExtractor& extractor, boost::asio::yield_context* yc) { static_assert(std::is_same_v || std::is_same_v, "Container must be a Namespace or Dictionary"); BeginContainer('{'); - ObjectLock olock(container); + auto olock = container->LockIfRequired(); + if (olock) { + yc = nullptr; // We've acquired an object lock, never allow asynchronous operations. + } + bool isEmpty = true; for (const auto& [key, val] : container) { WriteSeparatorAndIndentStrIfNeeded(!isEmpty); @@ -143,7 +168,9 @@ void JsonEncoder::EncodeObject(const Iterable& container, const ValExtractor& ex EncodeNlohmannJson(Utility::ValidateUTF8(key)); Write(m_Pretty ? ": " : ":"); - Encode(extractor(val)); + + Encode(extractor(val), yc); + FlushIfSafe(yc); } EndContainer('}', isEmpty); } @@ -193,6 +220,29 @@ void JsonEncoder::EncodeNumber(double value) const EncodeNlohmannJson(value); } +/** + * Flushes the output stream if it is safe to do so. + * + * Safe flushing means that it only performs the flush operation if the @c JsonEncoder has not acquired + * any object lock so far. This is to ensure that the stream can safely perform asynchronous operations + * without risking undefined behaviour due to coroutines being suspended while the stream is being flushed. + * + * When the @c yc parameter is provided, it indicates that it's safe to perform asynchronous operations, + * and the function will attempt to flush if the writer is an instance of @c AsyncJsonWriter. + * + * @param yc The yield context to use for asynchronous operations. + */ +void JsonEncoder::FlushIfSafe(boost::asio::yield_context* yc) const +{ + if (yc && m_IsAsyncWriter) { + // The m_IsAsyncWriter flag is a constant, and it will never change, so we can safely static + // cast the m_Writer to AsyncJsonWriter without any additional checks as it is guaranteed + // to be an instance of AsyncJsonWriter when m_IsAsyncWriter is true. + auto ajw(static_cast(m_Writer.get())); + ajw->Flush(*yc); + } +} + /** * Writes a string to the underlying output stream. * diff --git a/lib/base/json.hpp b/lib/base/json.hpp index 63828316c..464a926f5 100644 --- a/lib/base/json.hpp +++ b/lib/base/json.hpp @@ -73,18 +73,20 @@ public: explicit JsonEncoder(std::basic_ostream& stream, bool prettify = false); explicit JsonEncoder(nlohmann::detail::output_adapter_t w, bool prettify = false); - void Encode(const Value& value); + void Encode(const Value& value, boost::asio::yield_context* yc = nullptr); private: - void EncodeArray(const Array::Ptr& array); - void EncodeValueGenerator(const ValueGenerator::Ptr& generator); + void EncodeArray(const Array::Ptr& array, boost::asio::yield_context* yc); + void EncodeValueGenerator(const ValueGenerator::Ptr& generator, boost::asio::yield_context* yc); template - void EncodeObject(const Iterable& container, const ValExtractor& extractor); + void EncodeObject(const Iterable& container, const ValExtractor& extractor, boost::asio::yield_context* yc); void EncodeNlohmannJson(const nlohmann::json& json) const; void EncodeNumber(double value) const; + void FlushIfSafe(boost::asio::yield_context* yc) const; + void Write(const std::string_view& sv) const; void BeginContainer(char openChar); void EndContainer(char closeChar, bool isContainerEmpty = false); @@ -93,6 +95,7 @@ private: // The number of spaces to use for indentation in prettified JSON. static constexpr uint8_t m_IndentSize = 4; + const bool m_IsAsyncWriter; // Whether the writer is an instance of AsyncJsonWriter. bool m_Pretty; // Whether to pretty-print the JSON output. unsigned m_Indent{0}; // The current indentation level for pretty-printing. /** From 89418f38ee845c1d67c006c4d3fff77791c1b6ff Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 4 Jul 2025 16:51:26 +0200 Subject: [PATCH 381/415] JsonEncoder: let the serializer replace invalid UTF-8 characters Replacing invalid UTF-8 characters beforehand by our selves doesn't make any sense, the serializer can literally perform the same replacement ops with the exact same Unicode replacement character (U+FFFD) on its own. So, why not just use it directly? Instead of wasting memory on a temporary `String` object to always UTF-8 validate every and each value, we just use the serializer to directly to dump the replaced char (if any) into the output writer. No memory waste, no fuss! --- lib/base/json.cpp | 13 ++++++++----- lib/base/json.hpp | 4 +--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/base/json.cpp b/lib/base/json.cpp index e2749bbde..00d114731 100644 --- a/lib/base/json.cpp +++ b/lib/base/json.cpp @@ -5,6 +5,7 @@ #include "base/dictionary.hpp" #include "base/namespace.hpp" #include "base/objectlock.hpp" +#include "base/utility.hpp" #include #include #include @@ -56,7 +57,7 @@ void JsonEncoder::Encode(const Value& value, boost::asio::yield_context* yc) Write(value.ToBool() ? "true" : "false"); break; case ValueString: - EncodeNlohmannJson(Utility::ValidateUTF8(value.Get())); + EncodeNlohmannJson(value.Get()); break; case ValueNumber: EncodeNumber(value.Get()); @@ -76,7 +77,7 @@ void JsonEncoder::Encode(const Value& value, boost::asio::yield_context* yc) EncodeValueGenerator(gen, yc); } else { // Some other non-serializable object type! - EncodeNlohmannJson(Utility::ValidateUTF8(obj->ToString())); + EncodeNlohmannJson(obj->ToString()); } break; } @@ -166,7 +167,7 @@ void JsonEncoder::EncodeObject(const Iterable& container, const ValExtractor& ex WriteSeparatorAndIndentStrIfNeeded(!isEmpty); isEmpty = false; - EncodeNlohmannJson(Utility::ValidateUTF8(key)); + EncodeNlohmannJson(key); Write(m_Pretty ? ": " : ":"); Encode(extractor(val), yc); @@ -179,13 +180,15 @@ void JsonEncoder::EncodeObject(const Iterable& container, const ValExtractor& ex * Dumps a nlohmann::json object to the output stream using the serializer. * * This function uses the @c nlohmann::detail::serializer to dump the provided @c nlohmann::json - * object to the output stream managed by the @c JsonEncoder. + * object to the output stream managed by the @c JsonEncoder. Strings will be properly escaped, and + * if any invalid UTF-8 sequences are encountered, it will replace them with the Unicode replacement + * character (U+FFFD). * * @param json The nlohmann::json object to encode. */ void JsonEncoder::EncodeNlohmannJson(const nlohmann::json& json) const { - nlohmann::detail::serializer s(m_Writer, ' ', nlohmann::json::error_handler_t::strict); + nlohmann::detail::serializer s(m_Writer, ' ', nlohmann::json::error_handler_t::replace); s.dump(json, m_Pretty, true, 0, 0); } diff --git a/lib/base/json.hpp b/lib/base/json.hpp index 464a926f5..238c9bc53 100644 --- a/lib/base/json.hpp +++ b/lib/base/json.hpp @@ -6,7 +6,6 @@ #include "base/i2-base.hpp" #include "base/array.hpp" #include "base/generator.hpp" -#include "base/utility.hpp" #include #include @@ -58,8 +57,7 @@ class Value; * The JSON encoder generates most of the low level JSON tokens, but it still relies on the already existing * @c nlohmann::detail::serializer<> class to dump numbers and ASCII validated JSON strings. This means that the * encoder doesn't perform any kind of JSON validation or escaping on its own, but simply delegates all this kind - * of work to serializer<>. However, Strings are UTF-8 validated beforehand using the @c Utility::ValidateUTF8() - * function and only the validated (copy of the original) String is passed to the serializer. + * of work to serializer<>. * * The generated JSON can be either prettified or compact, depending on your needs. The prettified JSON object * is indented with 4 spaces and grows linearly with the depth of the object tree. From cd1ab7548cc7d1486cae95870b4f8dde52fbee8b Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 11 Jul 2025 12:56:52 +0200 Subject: [PATCH 382/415] Rename `AsyncJsonWriter::Flush()` -> `MayFlush()` to reflect its usage --- lib/base/json.cpp | 2 +- lib/base/json.hpp | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/base/json.cpp b/lib/base/json.cpp index 00d114731..8149e3f82 100644 --- a/lib/base/json.cpp +++ b/lib/base/json.cpp @@ -242,7 +242,7 @@ void JsonEncoder::FlushIfSafe(boost::asio::yield_context* yc) const // cast the m_Writer to AsyncJsonWriter without any additional checks as it is guaranteed // to be an instance of AsyncJsonWriter when m_IsAsyncWriter is true. auto ajw(static_cast(m_Writer.get())); - ajw->Flush(*yc); + ajw->MayFlush(*yc); } } diff --git a/lib/base/json.hpp b/lib/base/json.hpp index 238c9bc53..badf4d7ef 100644 --- a/lib/base/json.hpp +++ b/lib/base/json.hpp @@ -17,7 +17,7 @@ namespace icinga * * All users of this class must ensure that the underlying output stream will not perform any asynchronous I/O * operations when the @c write_character() or @c write_characters() methods are called. They shall only perform - * such ops when the @c JsonEncoder allows them to do so by calling the @c Flush() method. + * such ops when the @c JsonEncoder allows them to do so by calling the @c MayFlush() method. * * @ingroup base */ @@ -25,7 +25,7 @@ class AsyncJsonWriter : public nlohmann::detail::output_adapter_protocol { public: /** - * Flush instructs the underlying output stream to write any buffered data to wherever it is supposed to go. + * It instructs the underlying output stream to write any buffered data to wherever it is supposed to go. * * The @c JsonEncoder allows the stream to even perform asynchronous operations in a safe manner by calling * this method with a dedicated @c boost::asio::yield_context object. The stream must not perform any async @@ -37,7 +37,7 @@ public: * * @param yield The yield context to use for asynchronous operations. */ - virtual void Flush(boost::asio::yield_context& yield) = 0; + virtual void MayFlush(boost::asio::yield_context& yield) = 0; }; class String; From 82b80e24c16497052e374f20dde46d7c912e611f Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Fri, 11 Jul 2025 12:58:01 +0200 Subject: [PATCH 383/415] fix comment --- lib/base/json.hpp | 4 +--- test/base-json.cpp | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/base/json.hpp b/lib/base/json.hpp index badf4d7ef..868f8f0ac 100644 --- a/lib/base/json.hpp +++ b/lib/base/json.hpp @@ -50,9 +50,7 @@ class Value; * The supported stream types include any @c std::ostream like objects and our own @c AsyncJsonWriter, which * allows writing JSON data to an Asio stream asynchronously. The nlohmann/json library already provides * full support for the former stream type, while the latter is fully implemented by our own and satisfies the - * @c nlohmann::detail::output_adapter_protocol<> interface as well. Therefore, any concrete implementation of - * @c AsyncJsonWriter may be used to write the produced JSON directly to an Asio either TCP or TLS stream without - * any additional buffering other than the one used by the Asio buffered_stream<> class internally. + * @c nlohmann::detail::output_adapter_protocol<> interface as well. * * The JSON encoder generates most of the low level JSON tokens, but it still relies on the already existing * @c nlohmann::detail::serializer<> class to dump numbers and ASCII validated JSON strings. This means that the diff --git a/test/base-json.cpp b/test/base-json.cpp index 8df282c89..4ae17bf09 100644 --- a/test/base-json.cpp +++ b/test/base-json.cpp @@ -32,7 +32,7 @@ BOOST_AUTO_TEST_CASE(encode) { "false", false }, // Use double max value to test JSON encoding of large numbers and trigger boost numeric_cast exceptions { "max_double", std::numeric_limits::max() }, - // Test the maximum number that can be exact represented by a double is 2^64-2048. + // Test the largest uint64_t value that has an exact double representation (2^64-2048). { "max_int_in_double", std::nextafter(std::pow(2, 64), 0.0) }, { "float", -1.25f }, { "float_without_fraction", 23.0f }, From 1f15f0ff07a8bcc1eb9824c969e59824bde3af82 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Fri, 11 Jul 2025 11:18:24 +0200 Subject: [PATCH 384/415] JsonEncoder: wrap writer for flushing This commit intruduces a small helper class that wraps any writer and provides a flush operation that performs the corresponding action if the writer is an AsyncJsonWriter and does nothing otherwise. --- lib/base/json.cpp | 61 ++++++++++++++++++++++++++--------------------- lib/base/json.hpp | 17 ++++++++++--- 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/lib/base/json.cpp b/lib/base/json.cpp index 8149e3f82..531ab45b2 100644 --- a/lib/base/json.cpp +++ b/lib/base/json.cpp @@ -24,7 +24,7 @@ JsonEncoder::JsonEncoder(std::basic_ostream& stream, bool prettify) } JsonEncoder::JsonEncoder(nlohmann::detail::output_adapter_t w, bool prettify) - : m_IsAsyncWriter{dynamic_cast(w.get()) != nullptr}, m_Pretty(prettify), m_Writer(std::move(w)) + : m_Pretty(prettify), m_Writer(std::move(w)), m_Flusher{m_Writer} { } @@ -112,7 +112,7 @@ void JsonEncoder::EncodeArray(const Array::Ptr& array, boost::asio::yield_contex WriteSeparatorAndIndentStrIfNeeded(!isEmpty); isEmpty = false; Encode(item, yc); - FlushIfSafe(yc); + m_Flusher.FlushIfSafe(yc); } EndContainer(']', isEmpty); } @@ -134,7 +134,7 @@ void JsonEncoder::EncodeValueGenerator(const ValueGenerator::Ptr& generator, boo WriteSeparatorAndIndentStrIfNeeded(!isEmpty); isEmpty = false; Encode(*result, yc); - FlushIfSafe(yc); + m_Flusher.FlushIfSafe(yc); } EndContainer(']', isEmpty); } @@ -171,7 +171,7 @@ void JsonEncoder::EncodeObject(const Iterable& container, const ValExtractor& ex Write(m_Pretty ? ": " : ":"); Encode(extractor(val), yc); - FlushIfSafe(yc); + m_Flusher.FlushIfSafe(yc); } EndContainer('}', isEmpty); } @@ -223,29 +223,6 @@ void JsonEncoder::EncodeNumber(double value) const EncodeNlohmannJson(value); } -/** - * Flushes the output stream if it is safe to do so. - * - * Safe flushing means that it only performs the flush operation if the @c JsonEncoder has not acquired - * any object lock so far. This is to ensure that the stream can safely perform asynchronous operations - * without risking undefined behaviour due to coroutines being suspended while the stream is being flushed. - * - * When the @c yc parameter is provided, it indicates that it's safe to perform asynchronous operations, - * and the function will attempt to flush if the writer is an instance of @c AsyncJsonWriter. - * - * @param yc The yield context to use for asynchronous operations. - */ -void JsonEncoder::FlushIfSafe(boost::asio::yield_context* yc) const -{ - if (yc && m_IsAsyncWriter) { - // The m_IsAsyncWriter flag is a constant, and it will never change, so we can safely static - // cast the m_Writer to AsyncJsonWriter without any additional checks as it is guaranteed - // to be an instance of AsyncJsonWriter when m_IsAsyncWriter is true. - auto ajw(static_cast(m_Writer.get())); - ajw->MayFlush(*yc); - } -} - /** * Writes a string to the underlying output stream. * @@ -313,6 +290,36 @@ void JsonEncoder::WriteSeparatorAndIndentStrIfNeeded(bool emitComma) const } } +/** + * Wraps any writer of type @c nlohmann::detail::output_adapter_t into a Flusher + * + * @param w The writer to wrap. + */ +JsonEncoder::Flusher::Flusher(const nlohmann::detail::output_adapter_t& w) + : m_AsyncWriter(dynamic_cast(w.get())) +{ +} + +/** + * Flushes the underlying writer if it supports that operation and is safe to do so. + * + * Safe flushing means that it only performs the flush operation if the @c JsonEncoder has not acquired + * any object lock so far. This is to ensure that the stream can safely perform asynchronous operations + * without risking undefined behaviour due to coroutines being suspended while the stream is being flushed. + * + * When the @c yc parameter is provided, it indicates that it's safe to perform asynchronous operations, + * and the function will attempt to flush if the writer is an instance of @c AsyncJsonWriter. Otherwise, + * this function does nothing. + * + * @param yc The yield context to use for asynchronous operations. + */ +void JsonEncoder::Flusher::FlushIfSafe(boost::asio::yield_context* yc) const +{ + if (yc && m_AsyncWriter) { + m_AsyncWriter->MayFlush(*yc); + } +} + class JsonSax : public nlohmann::json_sax { public: diff --git a/lib/base/json.hpp b/lib/base/json.hpp index 868f8f0ac..a5ed46280 100644 --- a/lib/base/json.hpp +++ b/lib/base/json.hpp @@ -81,8 +81,6 @@ private: void EncodeNlohmannJson(const nlohmann::json& json) const; void EncodeNumber(double value) const; - void FlushIfSafe(boost::asio::yield_context* yc) const; - void Write(const std::string_view& sv) const; void BeginContainer(char openChar); void EndContainer(char closeChar, bool isContainerEmpty = false); @@ -91,7 +89,6 @@ private: // The number of spaces to use for indentation in prettified JSON. static constexpr uint8_t m_IndentSize = 4; - const bool m_IsAsyncWriter; // Whether the writer is an instance of AsyncJsonWriter. bool m_Pretty; // Whether to pretty-print the JSON output. unsigned m_Indent{0}; // The current indentation level for pretty-printing. /** @@ -104,6 +101,20 @@ private: // The output stream adapter for writing JSON data. This can be either a std::ostream or an Asio stream adapter. nlohmann::detail::output_adapter_t m_Writer; + + /** + * This class wraps any @c nlohmann::detail::output_adapter_t writer and provides a method to flush it as + * required. Only @c AsyncJsonWriter supports the flush operation, however, this class is also safe to use with + * other writer types and the flush method does nothing for them. + */ + class Flusher { + public: + explicit Flusher(const nlohmann::detail::output_adapter_t& w); + void FlushIfSafe(boost::asio::yield_context* yc) const; + + private: + AsyncJsonWriter* m_AsyncWriter; + } m_Flusher; }; String JsonEncode(const Value& value, bool prettify = false); From 054149fe08da56de322facb297f25fc35230fd5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Tue, 22 Jul 2025 10:59:19 +0200 Subject: [PATCH 385/415] GHA: add Fedora 42 --- .github/workflows/linux.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index cbaccc6e6..def1729e2 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -36,6 +36,7 @@ jobs: - fedora:39 - fedora:40 - fedora:41 + - fedora:42 - opensuse/leap:15.5 - opensuse/leap:15.6 From 5c5785acb8cac03367346dc73cf77b5398aaeac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Tue, 22 Jul 2025 10:59:34 +0200 Subject: [PATCH 386/415] GHA: add SLES 15.7 --- .github/workflows/linux.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index cbaccc6e6..2c4e9f313 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -47,6 +47,7 @@ jobs: - registry.suse.com/suse/sle15:15.5 - registry.suse.com/suse/sle15:15.6 + - registry.suse.com/suse/sle15:15.7 - ubuntu:22.04 - ubuntu:24.04 From 1197672aad484a521df5e4afe4435fb28f3f4001 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Tue, 22 Jul 2025 10:59:45 +0200 Subject: [PATCH 387/415] GHA: add Ubuntu 25.04 --- .github/workflows/linux.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index cbaccc6e6..bb5b4b6ca 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -51,6 +51,7 @@ jobs: - ubuntu:22.04 - ubuntu:24.04 - ubuntu:24.10 + - ubuntu:25.04 platform: - linux/amd64 From 37f8e7ec0143ca13a864ddd603b6698e00a70d0e Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Tue, 22 Jul 2025 16:36:37 +0200 Subject: [PATCH 388/415] GHA: add RHEL 10 --- .github/workflows/linux.bash | 2 +- .github/workflows/linux.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/linux.bash b/.github/workflows/linux.bash index 9bf4a68bc..d0f0a16b5 100755 --- a/.github/workflows/linux.bash +++ b/.github/workflows/linux.bash @@ -60,7 +60,7 @@ case "$DISTRO" in libboost_{context,coroutine,filesystem,iostreams,program_options,regex,system,test,thread}-devel ;; - rockylinux:*) + *rockylinux:*) dnf install -y 'dnf-command(config-manager)' epel-release case "$DISTRO" in diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index cbaccc6e6..b0c644c8c 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -44,6 +44,7 @@ jobs: # We just use that RHEL clone to test the original. - rockylinux:8 - rockylinux:9 + - rockylinux/rockylinux:10 - registry.suse.com/suse/sle15:15.5 - registry.suse.com/suse/sle15:15.6 From 35f42fa5a38027b1c454f92bc14377bbacfbc1e1 Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Wed, 18 Jun 2025 11:58:18 +0200 Subject: [PATCH 389/415] Handle concurrent config package updates gracefully Previously, we used a simple boolean to track the state of the package updates, and didn't reset it back when the config validation was successful because it was assumed that if we successfully validated the config beforehand, then the worker would also successfully reload the config afterwards, and that the old worker would be terminated. However, this assumption is not always true due to a number of reasons that I can't even think of right now, but the most obvious one is that after we successfully validated the config, the config might have changed again before the worker was able to reload it. If that happens, then the new worker might fail to successfully validate the config due to the recent changes, in which case the old worker would remain active, and this flag would still be set to true, causing any subsequent requests to fail with a `423` until you manually restart the Icinga 2 service. So, in order to prevent such a situation, we are additionally tracking the last time a reload failed and allow to bypass the `m_RunningPackageUpdates` flag only if the last reload failed time was changed since the previous request. --- lib/remote/configstageshandler.cpp | 37 ++++++++++++++++++++++++++---- lib/remote/configstageshandler.hpp | 3 --- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/lib/remote/configstageshandler.cpp b/lib/remote/configstageshandler.cpp index 0dee5f25f..95d1b1b6e 100644 --- a/lib/remote/configstageshandler.cpp +++ b/lib/remote/configstageshandler.cpp @@ -12,7 +12,10 @@ using namespace icinga; REGISTER_URLHANDLER("/v1/config/stages", ConfigStagesHandler); -std::atomic ConfigStagesHandler::m_RunningPackageUpdates (false); +static bool l_RunningPackageUpdates(false); +// A timestamp that indicates the last time an Icinga 2 reload failed. +static double l_LastReloadFailedTime(0); +static std::mutex l_RunningPackageUpdatesMutex; // Protects the above two variables. bool ConfigStagesHandler::HandleRequest( const WaitGroup::Ptr&, @@ -132,12 +135,36 @@ void ConfigStagesHandler::HandlePost( if (reload && !activate) BOOST_THROW_EXCEPTION(std::invalid_argument("Parameter 'reload' must be false when 'activate' is false.")); - if (m_RunningPackageUpdates.exchange(true)) { - return HttpUtility::SendJsonError(response, params, 423, - "Conflicting request, there is already an ongoing package update in progress. Please try it again later."); + { + std::lock_guard runningPackageUpdatesLock(l_RunningPackageUpdatesMutex); + double currentReloadFailedTime = Application::GetLastReloadFailed(); + + /** + * Once the m_RunningPackageUpdates flag is set, it typically remains set until the current worker process is + * terminated, in which case the new worker will have its own m_RunningPackageUpdates flag set to false. + * However, if the reload fails for any reason, the m_RunningPackageUpdates flag will remain set to true + * in the current worker process, which will prevent any further package updates from being processed until + * the next Icinga 2 restart. + * + * So, in order to prevent such a situation, we are additionally tracking the last time a reload failed + * and allow to bypass the m_RunningPackageUpdates flag only if the last reload failed time was changed + * since the previous request. + */ + if (l_RunningPackageUpdates && l_LastReloadFailedTime == currentReloadFailedTime) { + return HttpUtility::SendJsonError( + response, params, 423, + "Conflicting request, there is already an ongoing package update in progress. Please try it again later." + ); + } + + l_RunningPackageUpdates = true; + l_LastReloadFailedTime = currentReloadFailedTime; } - auto resetPackageUpdates (Shared::Make([]() { ConfigStagesHandler::m_RunningPackageUpdates.store(false); })); + auto resetPackageUpdates (Shared::Make([]() { + std::lock_guard lock(l_RunningPackageUpdatesMutex); + l_RunningPackageUpdates = false; + })); std::unique_lock lock(ConfigPackageUtility::GetStaticPackageMutex()); diff --git a/lib/remote/configstageshandler.hpp b/lib/remote/configstageshandler.hpp index a26ddc49c..a6abb726d 100644 --- a/lib/remote/configstageshandler.hpp +++ b/lib/remote/configstageshandler.hpp @@ -4,7 +4,6 @@ #define CONFIGSTAGESHANDLER_H #include "remote/httphandler.hpp" -#include namespace icinga { @@ -48,8 +47,6 @@ private: boost::beast::http::response& response, const Dictionary::Ptr& params ); - - static std::atomic m_RunningPackageUpdates; }; } From 1ac4d8396321976f58da743990af8829b9d3da4d Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 1 Jul 2025 12:00:34 +0200 Subject: [PATCH 390/415] Use `AtomicFile` where applicable in `ConfigPackageUtility` --- lib/remote/configpackageutility.cpp | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/lib/remote/configpackageutility.cpp b/lib/remote/configpackageutility.cpp index e79540147..949e69b00 100644 --- a/lib/remote/configpackageutility.cpp +++ b/lib/remote/configpackageutility.cpp @@ -3,6 +3,7 @@ #include "remote/configpackageutility.hpp" #include "remote/apilistener.hpp" #include "base/application.hpp" +#include "base/atomic-file.hpp" #include "base/exception.hpp" #include "base/utility.hpp" #include @@ -119,14 +120,9 @@ String ConfigPackageUtility::CreateStage(const String& packageName, const Dictio void ConfigPackageUtility::WritePackageConfig(const String& packageName) { String stageName = GetActiveStage(packageName); + AtomicFile::Write(GetPackageDir() + "/" + packageName + "/include.conf", 0644, "include \"*/include.conf\"\n"); - String includePath = GetPackageDir() + "/" + packageName + "/include.conf"; - std::ofstream fpInclude(includePath.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc); - fpInclude << "include \"*/include.conf\"\n"; - fpInclude.close(); - - String activePath = GetPackageDir() + "/" + packageName + "/active.conf"; - std::ofstream fpActive(activePath.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc); + AtomicFile fpActive(GetPackageDir() + "/" + packageName + "/active.conf", 0644); fpActive << "if (!globals.contains(\"ActiveStages\")) {\n" << " globals.ActiveStages = {}\n" << "}\n" @@ -145,19 +141,18 @@ void ConfigPackageUtility::WritePackageConfig(const String& packageName) << "if (!ActiveStages.contains(\"" << packageName << "\")) {\n" << " ActiveStages[\"" << packageName << "\"] = \"" << stageName << "\"\n" << "}\n"; - fpActive.close(); + fpActive.Commit(); } void ConfigPackageUtility::WriteStageConfig(const String& packageName, const String& stageName) { - String path = GetPackageDir() + "/" + packageName + "/" + stageName + "/include.conf"; - std::ofstream fp(path.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc); + AtomicFile fp(GetPackageDir() + "/" + packageName + "/" + stageName + "/include.conf", 0644); fp << "include \"../active.conf\"\n" << "if (ActiveStages[\"" << packageName << "\"] == \"" << stageName << "\") {\n" << " include_recursive \"conf.d\"\n" << " include_zones \"" << packageName << "\", \"zones.d\"\n" << "}\n"; - fp.close(); + fp.Commit(); } void ConfigPackageUtility::ActivateStage(const String& packageName, const String& stageName) @@ -284,12 +279,7 @@ String ConfigPackageUtility::GetActiveStageFromFile(const String& packageName) void ConfigPackageUtility::SetActiveStageToFile(const String& packageName, const String& stageName) { std::unique_lock lock(GetStaticActiveStageMutex()); - - String activeStagePath = GetPackageDir() + "/" + packageName + "/active-stage"; - - std::ofstream fpActiveStage(activeStagePath.CStr(), std::ofstream::out | std::ostream::binary | std::ostream::trunc); //TODO: fstream exceptions - fpActiveStage << stageName; - fpActiveStage.close(); + AtomicFile::Write(GetPackageDir() + "/" + packageName + "/active-stage", 0644, stageName); } String ConfigPackageUtility::GetActiveStage(const String& packageName) From ce3275d27f710bc239b914204e15ecae5540637f Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 22 Jul 2025 11:25:03 +0200 Subject: [PATCH 391/415] Disallow stage deletions during reload Once the new worker process has read the config, it also includes a `include */include.conf` statement within the config packages root directory, and from there on we must not allow to delete any stage directory from the config package. Otherwise, when the worker actually evaluates that include statement, it will fail to find the directory where the include file is located, or the `active.conf` file, which is included from each stage's `include.conf` file, thus causing the worker fail. Co-Authored-By: Johannes Schmidt --- lib/remote/configpackageshandler.cpp | 13 +++++++++++++ lib/remote/configstageshandler.cpp | 13 +++++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/remote/configpackageshandler.cpp b/lib/remote/configpackageshandler.cpp index 7987092bc..f24f5b1d2 100644 --- a/lib/remote/configpackageshandler.cpp +++ b/lib/remote/configpackageshandler.cpp @@ -2,6 +2,7 @@ #include "remote/configpackageshandler.hpp" #include "remote/configpackageutility.hpp" +#include "remote/configobjectslock.hpp" #include "remote/httputility.hpp" #include "remote/filterutility.hpp" #include "base/exception.hpp" @@ -111,6 +112,12 @@ void ConfigPackagesHandler::HandlePost( return; } + ConfigObjectsSharedLock configObjectsSharedLock(std::try_to_lock); + if (!configObjectsSharedLock) { + HttpUtility::SendJsonError(response, params, 503, "Icinga is reloading"); + return; + } + try { std::unique_lock lock(ConfigPackageUtility::GetStaticPackageMutex()); @@ -157,6 +164,12 @@ void ConfigPackagesHandler::HandleDelete( return; } + ConfigObjectsSharedLock lock(std::try_to_lock); + if (!lock) { + HttpUtility::SendJsonError(response, params, 503, "Icinga is reloading"); + return; + } + try { ConfigPackageUtility::DeletePackage(packageName); } catch (const std::exception& ex) { diff --git a/lib/remote/configstageshandler.cpp b/lib/remote/configstageshandler.cpp index 95d1b1b6e..451ba1dbf 100644 --- a/lib/remote/configstageshandler.cpp +++ b/lib/remote/configstageshandler.cpp @@ -2,6 +2,7 @@ #include "remote/configstageshandler.hpp" #include "remote/configpackageutility.hpp" +#include "remote/configobjectslock.hpp" #include "remote/httputility.hpp" #include "remote/filterutility.hpp" #include "base/application.hpp" @@ -135,6 +136,12 @@ void ConfigStagesHandler::HandlePost( if (reload && !activate) BOOST_THROW_EXCEPTION(std::invalid_argument("Parameter 'reload' must be false when 'activate' is false.")); + ConfigObjectsSharedLock configObjectsSharedLock(std::try_to_lock); + if (!configObjectsSharedLock) { + HttpUtility::SendJsonError(response, params, 503, "Icinga is reloading"); + return; + } + { std::lock_guard runningPackageUpdatesLock(l_RunningPackageUpdatesMutex); double currentReloadFailedTime = Application::GetLastReloadFailed(); @@ -228,6 +235,12 @@ void ConfigStagesHandler::HandleDelete( if (!ConfigPackageUtility::ValidateStageName(stageName)) return HttpUtility::SendJsonError(response, params, 400, "Invalid stage name '" + stageName + "'."); + ConfigObjectsSharedLock lock(std::try_to_lock); + if (!lock) { + HttpUtility::SendJsonError(response, params, 503, "Icinga is reloading"); + return; + } + try { ConfigPackageUtility::DeleteStage(packageName, stageName); } catch (const std::exception& ex) { From e86dd0d28edb8898a24d8dea06914fecb55df209 Mon Sep 17 00:00:00 2001 From: Markus Opolka Date: Tue, 22 Jul 2025 17:04:27 +0200 Subject: [PATCH 392/415] docs: Mention field mapping issue with ElasticsearchWriter --- doc/09-object-types.md | 5 +++++ doc/14-features.md | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/doc/09-object-types.md b/doc/09-object-types.md index 89b6ce102..66c773b77 100644 --- a/doc/09-object-types.md +++ b/doc/09-object-types.md @@ -1229,6 +1229,11 @@ Configuration Attributes: Note: If `flush_threshold` is set too low, this will force the feature to flush all data to Elasticsearch too often. Experiment with the setting, if you are processing more than 1024 metrics per second or similar. +> **Note** +> +> Be aware that `enable_send_perfdata` will create a new field mapping in the index for each performance data metric in a check plugin. +> Elasticsearch/OpenSearch have a maximum number of fields in an index. The default value is usually 1000 fields. See [mapping settings limit](https://www.elastic.co/guide/en/elasticsearch/reference/8.18/mapping-settings-limit.html) + Basic auth is supported with the `username` and `password` attributes. This requires an HTTP proxy (Nginx, etc.) in front of the Elasticsearch instance. Check [this blogpost](https://blog.netways.de/2017/09/14/secure-elasticsearch-and-kibana-with-an-nginx-http-proxy/) for an example. diff --git a/doc/14-features.md b/doc/14-features.md index 843aa20f4..31697b27e 100644 --- a/doc/14-features.md +++ b/doc/14-features.md @@ -364,7 +364,8 @@ The following event types are written to Elasticsearch: * icinga2.event.notification Performance data metrics must be explicitly enabled with the `enable_send_perfdata` -attribute. +attribute. Be aware that this will create a new field mapping in the index for each performance data metric in a check plugin. +See: [ElasticsearchWriter](09-object-types.md#objecttype-elasticsearchwriter) Metric values are stored like this: From 5605316850b3a90610decdf380108f158ba0d9ab Mon Sep 17 00:00:00 2001 From: Markus Opolka Date: Fri, 25 Jul 2025 09:50:50 +0200 Subject: [PATCH 393/415] docs: Update ElasticsearchWriter flush_threshold note format --- doc/09-object-types.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/09-object-types.md b/doc/09-object-types.md index 66c773b77..17583f871 100644 --- a/doc/09-object-types.md +++ b/doc/09-object-types.md @@ -1685,11 +1685,11 @@ Configuration Attributes: flush\_threshold | Number | **Optional.** How many data points to buffer before forcing a transfer to InfluxDB. Defaults to `1024`. enable\_ha | Boolean | **Optional.** Enable the high availability functionality. Only valid in a [cluster setup](06-distributed-monitoring.md#distributed-monitoring-high-availability-features). Defaults to `false`. -Note: If `flush_threshold` is set too low, this will always force the feature to flush all data -to InfluxDB. Experiment with the setting, if you are processing more than 1024 metrics per second -or similar. - - +> **Note** +> +> If `flush_threshold` is set too low, this will always force the feature to flush all data +> to InfluxDB. Experiment with the setting, if you are processing more than 1024 metrics per second +> or similar. ### Influxdb2Writer From 45721dcfc76ea5008563aaebb0e0e0b5219958d7 Mon Sep 17 00:00:00 2001 From: Markus Opolka Date: Wed, 23 Jul 2025 09:41:16 +0200 Subject: [PATCH 394/415] docs: Fix some dead/old links --- doc/13-addons.md | 2 +- doc/14-features.md | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/doc/13-addons.md b/doc/13-addons.md index d9c1c66cd..dfedc4ccc 100644 --- a/doc/13-addons.md +++ b/doc/13-addons.md @@ -122,7 +122,7 @@ icinga2 feature enable influxdb2 A popular frontend for InfluxDB is for example [Grafana](https://grafana.org). -Integration in Icinga Web 2 is possible by installing the community [Grafana module](https://github.com/Mikesch-mp/icingaweb2-module-grafana). +Integration in Icinga Web 2 is possible by installing the community [Grafana module](https://github.com/NETWAYS/icingaweb2-module-grafana). ![Icinga Web 2 Detail View with Grafana](images/addons/icingaweb2_grafana.png) diff --git a/doc/14-features.md b/doc/14-features.md index 31697b27e..5aa775c42 100644 --- a/doc/14-features.md +++ b/doc/14-features.md @@ -147,7 +147,7 @@ parsed from plugin output: Note that labels may contain dots (`.`) allowing to add more subsequent levels inside the Graphite tree. -`::` adds support for [multi performance labels](http://my-plugin.de/wiki/projects/check_multi/configuration/performance) +`::` adds support for [multi performance labels](https://github.com/flackem/check_multi/blob/next/doc/configuration/performance.md) and is therefore replaced by `.`. By enabling `enable_send_thresholds` Icinga 2 automatically adds the following threshold metrics: @@ -384,7 +384,7 @@ The following characters are escaped in perfdata labels: Note that perfdata labels may contain dots (`.`) allowing to add more subsequent levels inside the tree. -`::` adds support for [multi performance labels](http://my-plugin.de/wiki/projects/check_multi/configuration/performance) +`::` adds support for [multi performance labels](https://github.com/flackem/check_multi/blob/next/doc/configuration/performance.md) and is therefore replaced by `.`. Icinga 2 automatically adds the following threshold metrics @@ -443,11 +443,11 @@ or Logstash for additional filtering. #### GELF Writer -The `Graylog Extended Log Format` (short: [GELF](https://docs.graylog.org/en/latest/pages/gelf.html)) +The `Graylog Extended Log Format` (short: GELF) can be used to send application logs directly to a TCP socket. While it has been specified by the [Graylog](https://www.graylog.org) project as their -[input resource standard](https://docs.graylog.org/en/latest/pages/sending_data.html), other tools such as +[input resource standard](https://go2docs.graylog.org/current/getting_in_log_data/inputs.htm), other tools such as [Logstash](https://www.elastic.co/products/logstash) also support `GELF` as [input type](https://www.elastic.co/guide/en/logstash/current/plugins-inputs-gelf.html). @@ -1119,7 +1119,7 @@ As with any application database, there are ways to optimize and tune the databa General tips for performance tuning: -* [MariaDB KB](https://mariadb.com/kb/en/library/optimization-and-tuning/) +* [MariaDB KB](https://mariadb.com/docs/server/ha-and-performance/optimization-and-tuning) * [PostgreSQL Wiki](https://wiki.postgresql.org/wiki/Performance_Optimization) Re-creation of indexes, changed column values, etc. will increase the database size. Ensure to @@ -1236,7 +1236,7 @@ on the [Icinga 1.x documentation](https://docs.icinga.com/latest/en/extcommands2 > This feature is DEPRECATED and may be removed in future releases. > Check the [roadmap](https://github.com/Icinga/icinga2/milestones). -The [MK Livestatus](https://mathias-kettner.de/checkmk_livestatus.html) project +The [MK Livestatus](https://exchange.nagios.org/directory/Documentation/MK-Livestatus/details) project implements a query protocol that lets users query their Icinga instance for status information. It can also be used to send commands. From 64874976653b7eabf6d5e1136026078779d35a2b Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Mon, 14 Jul 2025 12:33:50 +0200 Subject: [PATCH 395/415] Log: use std::forward in operator<< and remove overload for const char* There already is a template operator<< implemented, so far only for const references though. Changing this to perfectly forward the argument to the corresponding operator in the underlying std::ostringstring allows handling all the cases there, removing the need for a separate overload for const char*. --- lib/base/logger.cpp | 10 +--------- lib/base/logger.hpp | 7 +++---- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/lib/base/logger.cpp b/lib/base/logger.cpp index 38a2c6721..b3be523a8 100644 --- a/lib/base/logger.cpp +++ b/lib/base/logger.cpp @@ -27,6 +27,7 @@ template Log& Log::operator<<(const int&); template Log& Log::operator<<(const unsigned long&); template Log& Log::operator<<(const long&); template Log& Log::operator<<(const double&); +template Log& Log::operator<<(const char*&); REGISTER_TYPE(Logger); @@ -315,12 +316,3 @@ Log::~Log() } #endif /* _WIN32 */ } - -Log& Log::operator<<(const char *val) -{ - if (!m_IsNoOp) { - m_Buffer << val; - } - - return *this; -} diff --git a/lib/base/logger.hpp b/lib/base/logger.hpp index 7b4758d8b..0437ebf39 100644 --- a/lib/base/logger.hpp +++ b/lib/base/logger.hpp @@ -119,17 +119,15 @@ public: ~Log(); template - Log& operator<<(const T& val) + Log& operator<<(T&& val) { if (!m_IsNoOp) { - m_Buffer << val; + m_Buffer << std::forward(val); } return *this; } - Log& operator<<(const char *val); - private: LogSeverity m_Severity; String m_Facility; @@ -146,6 +144,7 @@ extern template Log& Log::operator<<(const int&); extern template Log& Log::operator<<(const unsigned long&); extern template Log& Log::operator<<(const long&); extern template Log& Log::operator<<(const double&); +extern template Log& Log::operator<<(const char*&); } From ebd4fd193391a883c6e26fcd56b5f9dd5d57203d Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Mon, 14 Jul 2025 13:37:36 +0200 Subject: [PATCH 396/415] Log: don't construct std::ostringstream for no-op messages This commit removes the existing m_IsNoOp bool and instead wraps the m_Buffer std::ostringstream into std::optional. Functionally, this is pretty much the same, with the exception that std::ostringstream is no longer constructed for messages that will be discarded later. --- lib/base/logger.cpp | 18 +++++++++++------- lib/base/logger.hpp | 12 ++++++++---- 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/lib/base/logger.cpp b/lib/base/logger.cpp index b3be523a8..f28a19bbd 100644 --- a/lib/base/logger.cpp +++ b/lib/base/logger.cpp @@ -247,21 +247,25 @@ void Logger::UpdateMinLogSeverity() Log::Log(LogSeverity severity, String facility, const String& message) : Log(severity, std::move(facility)) { - if (!m_IsNoOp) { - m_Buffer << message; - } + *this << message; } Log::Log(LogSeverity severity, String facility) - : m_Severity(severity), m_Facility(std::move(facility)), m_IsNoOp(severity < Logger::GetMinLogSeverity()) -{ } +{ + // Only fully initialize the object if it's actually going to be logged. + if (severity >= Logger::GetMinLogSeverity()) { + m_Severity = severity; + m_Facility = std::move(facility); + m_Buffer.emplace(); + } +} /** * Writes the message to the application's log. */ Log::~Log() { - if (m_IsNoOp) { + if (!m_Buffer) { return; } @@ -271,7 +275,7 @@ Log::~Log() entry.Facility = m_Facility; { - auto msg (m_Buffer.str()); + auto msg (m_Buffer->str()); msg.erase(msg.find_last_not_of("\n") + 1u); entry.Message = std::move(msg); diff --git a/lib/base/logger.hpp b/lib/base/logger.hpp index 0437ebf39..d7f30bcee 100644 --- a/lib/base/logger.hpp +++ b/lib/base/logger.hpp @@ -6,6 +6,7 @@ #include "base/atomic.hpp" #include "base/i2-base.hpp" #include "base/logger-ti.hpp" +#include #include #include @@ -121,8 +122,8 @@ public: template Log& operator<<(T&& val) { - if (!m_IsNoOp) { - m_Buffer << std::forward(val); + if (m_Buffer) { + *m_Buffer << std::forward(val); } return *this; @@ -131,8 +132,11 @@ public: private: LogSeverity m_Severity; String m_Facility; - std::ostringstream m_Buffer; - bool m_IsNoOp; + /** + * Stream for incrementally generating the log message. If the message will be discarded as it's level currently + * isn't logged, it will be empty as the stream doesn't need to be initialized in this case. + */ + std::optional m_Buffer; }; extern template Log& Log::operator<<(const Value&); From a49ec1015da69692bcdc674a455c9ef8737a3224 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Wed, 23 Jul 2025 13:06:55 +0200 Subject: [PATCH 397/415] Allow intrusive_ptr for objects This allows using ref-counted pointers to const objects. Adds a second typedef so that T::ConstPtr can be used similar to how T::Ptr currently is. --- lib/base/object.cpp | 8 ++++---- lib/base/object.hpp | 17 +++++++++-------- lib/base/shared-object.hpp | 14 +++++++------- lib/base/shared.hpp | 11 ++++++----- 4 files changed, 26 insertions(+), 24 deletions(-) diff --git a/lib/base/object.cpp b/lib/base/object.cpp index 5c7c67a8e..0c1e69e8e 100644 --- a/lib/base/object.cpp +++ b/lib/base/object.cpp @@ -201,14 +201,14 @@ Value icinga::GetPrototypeField(const Value& context, const String& field, bool } #ifdef I2_LEAK_DEBUG -void icinga::TypeAddObject(Object *object) +void icinga::TypeAddObject(const Object *object) { std::unique_lock lock(l_ObjectCountLock); String typeName = Utility::GetTypeName(typeid(*object)); l_ObjectCounts[typeName]++; } -void icinga::TypeRemoveObject(Object *object) +void icinga::TypeRemoveObject(const Object *object) { std::unique_lock lock(l_ObjectCountLock); String typeName = Utility::GetTypeName(typeid(*object)); @@ -239,7 +239,7 @@ INITIALIZE_ONCE([]() { }); #endif /* I2_LEAK_DEBUG */ -void icinga::intrusive_ptr_add_ref(Object *object) +void icinga::intrusive_ptr_add_ref(const Object *object) { #ifdef I2_LEAK_DEBUG if (object->m_References.fetch_add(1) == 0u) @@ -249,7 +249,7 @@ void icinga::intrusive_ptr_add_ref(Object *object) #endif /* I2_LEAK_DEBUG */ } -void icinga::intrusive_ptr_release(Object *object) +void icinga::intrusive_ptr_release(const Object *object) { auto previous (object->m_References.fetch_sub(1)); diff --git a/lib/base/object.hpp b/lib/base/object.hpp index 7f520672e..dc08db043 100644 --- a/lib/base/object.hpp +++ b/lib/base/object.hpp @@ -31,7 +31,8 @@ class ValidationUtils; extern const Value Empty; #define DECLARE_PTR_TYPEDEFS(klass) \ - typedef intrusive_ptr Ptr + typedef intrusive_ptr Ptr; \ + typedef intrusive_ptr ConstPtr #define IMPL_TYPE_LOOKUP_SUPER() \ @@ -192,7 +193,7 @@ private: Object(const Object& other) = delete; Object& operator=(const Object& rhs) = delete; - std::atomic m_References; + mutable std::atomic m_References; mutable std::recursive_mutex m_Mutex; #ifdef I2_DEBUG @@ -202,17 +203,17 @@ private: friend struct ObjectLock; - friend void intrusive_ptr_add_ref(Object *object); - friend void intrusive_ptr_release(Object *object); + friend void intrusive_ptr_add_ref(const Object *object); + friend void intrusive_ptr_release(const Object *object); }; Value GetPrototypeField(const Value& context, const String& field, bool not_found_error, const DebugInfo& debugInfo); -void TypeAddObject(Object *object); -void TypeRemoveObject(Object *object); +void TypeAddObject(const Object *object); +void TypeRemoveObject(const Object *object); -void intrusive_ptr_add_ref(Object *object); -void intrusive_ptr_release(Object *object); +void intrusive_ptr_add_ref(const Object *object); +void intrusive_ptr_release(const Object *object); template class ObjectImpl diff --git a/lib/base/shared-object.hpp b/lib/base/shared-object.hpp index 396969db6..a434f404c 100644 --- a/lib/base/shared-object.hpp +++ b/lib/base/shared-object.hpp @@ -12,8 +12,8 @@ namespace icinga class SharedObject; -inline void intrusive_ptr_add_ref(SharedObject *object); -inline void intrusive_ptr_release(SharedObject *object); +inline void intrusive_ptr_add_ref(const SharedObject *object); +inline void intrusive_ptr_release(const SharedObject *object); /** * Seamless and polymorphistic base for any class to create shared pointers of. @@ -23,8 +23,8 @@ inline void intrusive_ptr_release(SharedObject *object); */ class SharedObject { - friend void intrusive_ptr_add_ref(SharedObject *object); - friend void intrusive_ptr_release(SharedObject *object); + friend void intrusive_ptr_add_ref(const SharedObject *object); + friend void intrusive_ptr_release(const SharedObject *object); protected: inline SharedObject() : m_References(0) @@ -38,15 +38,15 @@ protected: ~SharedObject() = default; private: - Atomic m_References; + mutable Atomic m_References; }; -inline void intrusive_ptr_add_ref(SharedObject *object) +inline void intrusive_ptr_add_ref(const SharedObject *object) { object->m_References.fetch_add(1); } -inline void intrusive_ptr_release(SharedObject *object) +inline void intrusive_ptr_release(const SharedObject *object) { if (object->m_References.fetch_sub(1) == 1u) { delete object; diff --git a/lib/base/shared.hpp b/lib/base/shared.hpp index 2acec012e..2c9d0fcee 100644 --- a/lib/base/shared.hpp +++ b/lib/base/shared.hpp @@ -16,13 +16,13 @@ template class Shared; template -inline void intrusive_ptr_add_ref(Shared *object) +inline void intrusive_ptr_add_ref(const Shared *object) { object->m_References.fetch_add(1); } template -inline void intrusive_ptr_release(Shared *object) +inline void intrusive_ptr_release(const Shared *object) { if (object->m_References.fetch_sub(1) == 1u) { delete object; @@ -38,11 +38,12 @@ inline void intrusive_ptr_release(Shared *object) template class Shared : public T { - friend void intrusive_ptr_add_ref<>(Shared *object); - friend void intrusive_ptr_release<>(Shared *object); + friend void intrusive_ptr_add_ref<>(const Shared *object); + friend void intrusive_ptr_release<>(const Shared *object); public: typedef boost::intrusive_ptr Ptr; + typedef boost::intrusive_ptr ConstPtr; /** * Like std::make_shared, but for this class. @@ -94,7 +95,7 @@ public: } private: - Atomic m_References; + mutable Atomic m_References; }; } From 43f1e6f3a1af0bdf4a83aafc8ab75d0687d406f1 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Mon, 28 Jul 2025 15:29:24 +0200 Subject: [PATCH 398/415] Move code involved in recursive dependency evaluation to helper class Checkable::IsReachable() and DependencyGroup::GetState() call each other recursively. Moving them to a common helper class allows adding caching to them in a later commit without having to pass a cache between the functions (through a public interface) or resorting to thread_local variables. --- lib/icinga/CMakeLists.txt | 2 +- lib/icinga/checkable-dependency.cpp | 50 +++--------- lib/icinga/checkable.hpp | 2 +- lib/icinga/dependency-group.cpp | 41 +--------- lib/icinga/dependency-state.cpp | 114 ++++++++++++++++++++++++++++ lib/icinga/dependency.hpp | 32 +++++++- 6 files changed, 160 insertions(+), 81 deletions(-) create mode 100644 lib/icinga/dependency-state.cpp diff --git a/lib/icinga/CMakeLists.txt b/lib/icinga/CMakeLists.txt index 2286fec94..de4e153a5 100644 --- a/lib/icinga/CMakeLists.txt +++ b/lib/icinga/CMakeLists.txt @@ -39,7 +39,7 @@ set(icinga_SOURCES comment.cpp comment.hpp comment-ti.hpp compatutility.cpp compatutility.hpp customvarobject.cpp customvarobject.hpp customvarobject-ti.hpp - dependency.cpp dependency-group.cpp dependency.hpp dependency-ti.hpp dependency-apply.cpp + dependency.cpp dependency-group.cpp dependency-state.cpp dependency.hpp dependency-ti.hpp dependency-apply.cpp downtime.cpp downtime.hpp downtime-ti.hpp envresolver.cpp envresolver.hpp eventcommand.cpp eventcommand.hpp eventcommand-ti.hpp diff --git a/lib/icinga/checkable-dependency.cpp b/lib/icinga/checkable-dependency.cpp index 65d9dd902..21690c2dd 100644 --- a/lib/icinga/checkable-dependency.cpp +++ b/lib/icinga/checkable-dependency.cpp @@ -6,14 +6,6 @@ using namespace icinga; -/** - * The maximum number of allowed dependency recursion levels. - * - * This is a subjective limit how deep the dependency tree should be allowed to go, as anything beyond this level - * is just madness and will likely result in a stack overflow or other undefined behavior. - */ -static constexpr int l_MaxDependencyRecursionLevel(256); - /** * Register all the dependency groups of the current Checkable to the global dependency group registry. * @@ -186,36 +178,15 @@ std::vector Checkable::GetReverseDependencies() const return std::vector(m_ReverseDependencies.begin(), m_ReverseDependencies.end()); } -bool Checkable::IsReachable(DependencyType dt, int rstack) const +/** + * Checks whether this checkable is currently reachable according to its dependencies. + * + * @param dt Dependency type to evaluate for. + * @return Whether the given checkable is reachable. + */ +bool Checkable::IsReachable(DependencyType dt) const { - if (rstack > l_MaxDependencyRecursionLevel) { - Log(LogWarning, "Checkable") - << "Too many nested dependencies (>" << l_MaxDependencyRecursionLevel << ") for checkable '" << GetName() << "': Dependency failed."; - - return false; - } - - /* implicit dependency on host if this is a service */ - const auto *service = dynamic_cast(this); - if (service && (dt == DependencyState || dt == DependencyNotification)) { - Host::Ptr host = service->GetHost(); - - if (host && host->GetState() != HostUp && host->GetStateType() == StateTypeHard) { - return false; - } - } - - for (auto& dependencyGroup : GetDependencyGroups()) { - if (auto state(dependencyGroup->GetState(this, dt, rstack + 1)); state != DependencyGroup::State::Ok) { - Log(LogDebug, "Checkable") - << "Dependency group '" << dependencyGroup->GetRedundancyGroupName() << "' have failed for checkable '" - << GetName() << "': Marking as unreachable."; - - return false; - } - } - - return true; + return DependencyStateChecker(dt).IsReachable(this); } /** @@ -307,9 +278,10 @@ std::set Checkable::GetAllChildren() const */ void Checkable::GetAllChildrenInternal(std::set& seenChildren, int level) const { - if (level > l_MaxDependencyRecursionLevel) { + if (level > Dependency::MaxDependencyRecursionLevel) { Log(LogWarning, "Checkable") - << "Too many nested dependencies (>" << l_MaxDependencyRecursionLevel << ") for checkable '" << GetName() << "': aborting traversal."; + << "Too many nested dependencies (>" << Dependency::MaxDependencyRecursionLevel << ") for checkable '" + << GetName() << "': aborting traversal."; return; } diff --git a/lib/icinga/checkable.hpp b/lib/icinga/checkable.hpp index 2ccc87073..796421a9b 100644 --- a/lib/icinga/checkable.hpp +++ b/lib/icinga/checkable.hpp @@ -84,7 +84,7 @@ public: void AddGroup(const String& name); - bool IsReachable(DependencyType dt = DependencyState, int rstack = 0) const; + bool IsReachable(DependencyType dt = DependencyState) const; bool AffectsChildren() const; AcknowledgementType GetAcknowledgement(); diff --git a/lib/icinga/dependency-group.cpp b/lib/icinga/dependency-group.cpp index aa7d5fc06..d60fec7c1 100644 --- a/lib/icinga/dependency-group.cpp +++ b/lib/icinga/dependency-group.cpp @@ -302,47 +302,10 @@ String DependencyGroup::GetCompositeKey() * * @param child The child Checkable to evaluate the state for. * @param dt The dependency type to evaluate the state for, defaults to DependencyState. - * @param rstack The recursion stack level to prevent infinite recursion, defaults to 0. * * @return - Returns the state of the current dependency group. */ -DependencyGroup::State DependencyGroup::GetState(const Checkable* child, DependencyType dt, int rstack) const +DependencyGroup::State DependencyGroup::GetState(const Checkable* child, DependencyType dt) const { - auto dependencies(GetDependenciesForChild(child)); - size_t reachable = 0, available = 0; - - for (const auto& dependency : dependencies) { - if (dependency->GetParent()->IsReachable(dt, rstack)) { - reachable++; - - // Only reachable parents are considered for availability. If they are unreachable and checks are - // disabled, they could be incorrectly treated as available otherwise. - if (dependency->IsAvailable(dt)) { - available++; - } - } - } - - if (IsRedundancyGroup()) { - // The state of a redundancy group is determined by the best state of any parent. If any parent ist reachable, - // the redundancy group is reachable, analogously for availability. - if (reachable == 0) { - return State::Unreachable; - } else if (available == 0) { - return State::Failed; - } else { - return State::Ok; - } - } else { - // For dependencies without a redundancy group, dependencies.size() will be 1 in almost all cases. It will only - // contain more elements if there are duplicate dependency config objects between two checkables. In this case, - // all of them have to be reachable/available as they don't provide redundancy. - if (reachable < dependencies.size()) { - return State::Unreachable; - } else if (available < dependencies.size()) { - return State::Failed; - } else { - return State::Ok; - } - } + return DependencyStateChecker(dt).GetState(this, child); } diff --git a/lib/icinga/dependency-state.cpp b/lib/icinga/dependency-state.cpp new file mode 100644 index 000000000..675347cac --- /dev/null +++ b/lib/icinga/dependency-state.cpp @@ -0,0 +1,114 @@ +/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */ + +#include "icinga/dependency.hpp" +#include "icinga/host.hpp" +#include "icinga/service.hpp" + +using namespace icinga; + +/** + * Construct a helper for evaluating the state of dependencies. + * + * @param dt Dependency type to check for within the individual methods. + */ +DependencyStateChecker::DependencyStateChecker(DependencyType dt) + : m_DependencyType(dt) +{ +} + +/** + * Checks whether a given checkable is currently reachable. + * + * @param checkable The checkable to check reachability for. + * @param rstack The recursion stack level to prevent infinite recursion, defaults to 0. + * @return Whether the given checkable is reachable. + */ +bool DependencyStateChecker::IsReachable(Checkable::ConstPtr checkable, int rstack) +{ + if (rstack > Dependency::MaxDependencyRecursionLevel) { + Log(LogWarning, "Checkable") + << "Too many nested dependencies (>" << Dependency::MaxDependencyRecursionLevel << ") for checkable '" + << checkable->GetName() << "': Dependency failed."; + + return false; + } + + /* implicit dependency on host if this is a service */ + const auto* service = dynamic_cast(checkable.get()); + if (service && (m_DependencyType == DependencyState || m_DependencyType == DependencyNotification)) { + Host::Ptr host = service->GetHost(); + + if (host && host->GetState() != HostUp && host->GetStateType() == StateTypeHard) { + return false; + } + } + + for (auto& dependencyGroup : checkable->GetDependencyGroups()) { + if (auto state(GetState(dependencyGroup, checkable.get(), rstack + 1)); state != DependencyGroup::State::Ok) { + Log(LogDebug, "Checkable") + << "Dependency group '" << dependencyGroup->GetRedundancyGroupName() << "' have failed for checkable '" + << checkable->GetName() << "': Marking as unreachable."; + + return false; + } + } + + return true; +} + +/** + * Retrieve the state of the given dependency group. + * + * The state of the dependency group is determined based on the state of the parent Checkables and dependency objects + * of the group. A dependency group is considered unreachable when none of the parent Checkables is reachable. However, + * a dependency group may still be marked as failed even when it has reachable parent Checkables, but an unreachable + * group has always a failed state. + * + * @param group + * @param child The child Checkable to evaluate the state for. + * @param rstack The recursion stack level to prevent infinite recursion, defaults to 0. + * + * @return - Returns the state of the current dependency group. + */ +DependencyGroup::State DependencyStateChecker::GetState(const DependencyGroup::ConstPtr& group, const Checkable* child, int rstack) +{ + using State = DependencyGroup::State; + + auto dependencies(group->GetDependenciesForChild(child)); + size_t reachable = 0, available = 0; + + for (const auto& dependency : dependencies) { + if (IsReachable(dependency->GetParent(), rstack)) { + reachable++; + + // Only reachable parents are considered for availability. If they are unreachable and checks are + // disabled, they could be incorrectly treated as available otherwise. + if (dependency->IsAvailable(m_DependencyType)) { + available++; + } + } + } + + if (group->IsRedundancyGroup()) { + // The state of a redundancy group is determined by the best state of any parent. If any parent ist reachable, + // the redundancy group is reachable, analogously for availability. + if (reachable == 0) { + return State::Unreachable; + } else if (available == 0) { + return State::Failed; + } else { + return State::Ok; + } + } else { + // For dependencies without a redundancy group, dependencies.size() will be 1 in almost all cases. It will only + // contain more elements if there are duplicate dependency config objects between two checkables. In this case, + // all of them have to be reachable/available as they don't provide redundancy. + if (reachable < dependencies.size()) { + return State::Unreachable; + } else if (available < dependencies.size()) { + return State::Failed; + } else { + return State::Ok; + } + } +} diff --git a/lib/icinga/dependency.hpp b/lib/icinga/dependency.hpp index b4e206b7f..1ff9ad387 100644 --- a/lib/icinga/dependency.hpp +++ b/lib/icinga/dependency.hpp @@ -49,6 +49,14 @@ public: void SetParent(intrusive_ptr parent); void SetChild(intrusive_ptr child); + /** + * The maximum number of allowed dependency recursion levels. + * + * This is a subjective limit how deep the dependency tree should be allowed to go, as anything beyond this level + * is just madness and will likely result in a stack overflow or other undefined behavior. + */ + static constexpr int MaxDependencyRecursionLevel{256}; + protected: void OnConfigLoaded() override; void OnAllConfigLoaded() override; @@ -164,7 +172,7 @@ public: String GetCompositeKey(); enum class State { Ok, Failed, Unreachable }; - State GetState(const Checkable* child, DependencyType dt = DependencyState, int rstack = 0) const; + State GetState(const Checkable* child, DependencyType dt = DependencyState) const; static boost::signals2::signal OnChildRegistered; static boost::signals2::signal&, bool)> OnChildRemoved; @@ -222,6 +230,28 @@ private: static RegistryType m_Registry; }; +/** + * Helper class to evaluate the reachability of checkables and state of dependency groups. + * + * This class is used for implementing Checkable::IsReachable() and DependencyGroup::GetState(). + * For this, both methods call each other, traversing the dependency graph recursively. In order + * to achieve linear runtime in the graph size, the class internally caches state information + * (otherwise, evaluating the state of the same checkable multiple times can result in exponential + * worst-case complexity). Because of this cached information is not invalidated, the object is + * intended to be short-lived. + */ +class DependencyStateChecker +{ +public: + explicit DependencyStateChecker(DependencyType dt); + + bool IsReachable(Checkable::ConstPtr checkable, int rstack = 0); + DependencyGroup::State GetState(const DependencyGroup::ConstPtr& group, const Checkable* child, int rstack = 0); + +private: + DependencyType m_DependencyType; +}; + } #endif /* DEPENDENCY_H */ From 63e9ef58ba6a0ed941b5d7389a436aa9dbe26046 Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Mon, 28 Jul 2025 15:32:05 +0200 Subject: [PATCH 399/415] Prevent worst-case exponential complexity in dependency evaluation So far, calling Checkable::IsReachable() traversed all possible paths to it's parents. In case a parent is reachable via multiple paths, all it's parents were evaluated multiple times, result in a worst-case exponential complexity. With this commit, the implementation keeps track of which checkables were already visited and uses the already-computed reachability instead of repeating the computation, ensuring a worst-case linear runtime within the graph size. --- lib/icinga/dependency-state.cpp | 11 +++++++++++ lib/icinga/dependency.hpp | 1 + 2 files changed, 12 insertions(+) diff --git a/lib/icinga/dependency-state.cpp b/lib/icinga/dependency-state.cpp index 675347cac..cd1548b42 100644 --- a/lib/icinga/dependency-state.cpp +++ b/lib/icinga/dependency-state.cpp @@ -25,6 +25,14 @@ DependencyStateChecker::DependencyStateChecker(DependencyType dt) */ bool DependencyStateChecker::IsReachable(Checkable::ConstPtr checkable, int rstack) { + // If the reachability of this checkable was already computed, return it directly. Otherwise, already create a + // temporary map entry that says that this checkable is unreachable so that the different cases returning false + // don't have to deal with updating the cache, but only the final return true does. Cyclic dependencies are invalid, + // hence recursive calls won't access the potentially not yet correct cached value. + if (auto [it, inserted] = m_Cache.insert({checkable, false}); !inserted) { + return it->second; + } + if (rstack > Dependency::MaxDependencyRecursionLevel) { Log(LogWarning, "Checkable") << "Too many nested dependencies (>" << Dependency::MaxDependencyRecursionLevel << ") for checkable '" @@ -53,6 +61,9 @@ bool DependencyStateChecker::IsReachable(Checkable::ConstPtr checkable, int rsta } } + // Note: This must do the map lookup again. The iterator from above must not be used as a m_Cache.insert() inside a + // recursive may have invalidated it. + m_Cache[checkable] = true; return true; } diff --git a/lib/icinga/dependency.hpp b/lib/icinga/dependency.hpp index 1ff9ad387..c70e578b4 100644 --- a/lib/icinga/dependency.hpp +++ b/lib/icinga/dependency.hpp @@ -250,6 +250,7 @@ public: private: DependencyType m_DependencyType; + std::unordered_map m_Cache; }; } From 9601468674a4f3d2a16e0d45428300636acd6e7c Mon Sep 17 00:00:00 2001 From: Julian Brost Date: Wed, 30 Jul 2025 16:39:13 +0200 Subject: [PATCH 400/415] Document current dependency recursion limit --- doc/03-monitoring-basics.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/03-monitoring-basics.md b/doc/03-monitoring-basics.md index 5e2710f9f..db7675354 100644 --- a/doc/03-monitoring-basics.md +++ b/doc/03-monitoring-basics.md @@ -3097,6 +3097,12 @@ via the [REST API](12-icinga2-api.md#icinga2-api). > Reachability calculation depends on fresh and processed check results. If dependencies > disable checks for child objects, this won't work reliably. +> **Note** +> +> The parent of a dependency can have a parent itself and so on. The nesting depth of +> dependencies is currently limited to 256 which should be more than enough for any practical +> use. This is an implementation detail and may change in the future. + ### Implicit Dependencies for Services on Host Icinga 2 automatically adds an implicit dependency for services on their host. That way From fdb471a09b2972364c3256df9224ea621cf382a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 12 Aug 2025 15:18:20 +0000 Subject: [PATCH 401/415] Bump actions/checkout from 4 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 5. - [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...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/authors-file.yml | 2 +- .github/workflows/linux.yml | 2 +- .github/workflows/windows.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/authors-file.yml b/.github/workflows/authors-file.yml index 724e189cb..28cc19821 100644 --- a/.github/workflows/authors-file.yml +++ b/.github/workflows/authors-file.yml @@ -10,7 +10,7 @@ jobs: steps: - name: Checkout HEAD - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index 3243fdb36..a5c9e9b2d 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -67,7 +67,7 @@ jobs: steps: - name: Checkout HEAD - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Restore/backup ccache uses: actions/cache@v4 diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index 052254897..be5ffe25e 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout HEAD - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 From 00295cb64b39aa25cb9ee535fa6a819245f7ec53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20Aleksandrovi=C4=8D=20Klimov?= Date: Wed, 13 Aug 2025 12:19:16 +0200 Subject: [PATCH 402/415] GHA: add Debian 13 --- .github/workflows/linux.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index a5c9e9b2d..fc25c365e 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -32,6 +32,7 @@ jobs: # Its architecture is different, though, and covered by the Docker job. - debian:11 - debian:12 + - debian:13 - fedora:39 - fedora:40 From 76fa0d9e8054f405dc3d1e39a4b48f21e86afdf0 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 14 Aug 2025 11:05:37 +0200 Subject: [PATCH 403/415] Boost: don't require "system" component It's vanished in v1.89. --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 86abc77d6..1a087b8ed 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -172,7 +172,7 @@ else() set(LOGROTATE_CREATE "\n\tcreate 644 ${ICINGA2_USER} ${ICINGA2_GROUP}") endif() -find_package(Boost ${BOOST_MIN_VERSION} COMPONENTS coroutine context date_time filesystem iostreams thread system program_options regex REQUIRED) +find_package(Boost ${BOOST_MIN_VERSION} COMPONENTS coroutine context date_time filesystem iostreams thread program_options regex REQUIRED) # Boost.Coroutine2 (the successor of Boost.Coroutine) # (1) doesn't even exist in old Boost versions and From 5a83f153b795a2a2099c12a9f8384b79a9b24302 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Thu, 14 Aug 2025 10:12:37 +0200 Subject: [PATCH 404/415] Bump Boost shipped for Windows to v1.89 --- doc/win-dev.ps1 | 2 +- tools/win32/configure-dev.ps1 | 4 ++-- tools/win32/configure.ps1 | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/win-dev.ps1 b/doc/win-dev.ps1 index 09ba714b9..2005389a3 100644 --- a/doc/win-dev.ps1 +++ b/doc/win-dev.ps1 @@ -13,7 +13,7 @@ function ThrowOnNativeFailure { $VsVersion = 2022 $MsvcVersion = '14.3' -$BoostVersion = @(1, 88, 0) +$BoostVersion = @(1, 89, 0) $OpensslVersion = '3_0_16' switch ($Env:BITS) { diff --git a/tools/win32/configure-dev.ps1 b/tools/win32/configure-dev.ps1 index 311804fa3..875065332 100644 --- a/tools/win32/configure-dev.ps1 +++ b/tools/win32/configure-dev.ps1 @@ -34,10 +34,10 @@ if (-not (Test-Path env:OPENSSL_ROOT_DIR)) { $env:OPENSSL_ROOT_DIR = 'c:\local\OpenSSL-Win64' } if (-not (Test-Path env:BOOST_ROOT)) { - $env:BOOST_ROOT = 'c:\local\boost_1_88_0' + $env:BOOST_ROOT = 'c:\local\boost_1_89_0' } if (-not (Test-Path env:BOOST_LIBRARYDIR)) { - $env:BOOST_LIBRARYDIR = 'c:\local\boost_1_88_0\lib64-msvc-14.3' + $env:BOOST_LIBRARYDIR = 'c:\local\boost_1_89_0\lib64-msvc-14.3' } if (-not (Test-Path env:FLEX_BINARY)) { $env:FLEX_BINARY = 'C:\ProgramData\chocolatey\bin\win_flex.exe' diff --git a/tools/win32/configure.ps1 b/tools/win32/configure.ps1 index 1cc8dfb3d..6321864a6 100644 --- a/tools/win32/configure.ps1 +++ b/tools/win32/configure.ps1 @@ -36,10 +36,10 @@ if (-not (Test-Path env:OPENSSL_ROOT_DIR)) { $env:OPENSSL_ROOT_DIR = "c:\local\OpenSSL_3_0_16-Win${env:BITS}" } if (-not (Test-Path env:BOOST_ROOT)) { - $env:BOOST_ROOT = "c:\local\boost_1_88_0-Win${env:BITS}" + $env:BOOST_ROOT = "c:\local\boost_1_89_0-Win${env:BITS}" } if (-not (Test-Path env:BOOST_LIBRARYDIR)) { - $env:BOOST_LIBRARYDIR = "c:\local\boost_1_88_0-Win${env:BITS}\lib${env:BITS}-msvc-14.3" + $env:BOOST_LIBRARYDIR = "c:\local\boost_1_89_0-Win${env:BITS}\lib${env:BITS}-msvc-14.3" } if (-not (Test-Path env:FLEX_BINARY)) { $env:FLEX_BINARY = 'C:\ProgramData\chocolatey\bin\win_flex.exe' From 17b49bd5b6ffaa65d81d6107d6eb450f03824400 Mon Sep 17 00:00:00 2001 From: "Alexander A. Klimov" Date: Fri, 15 Aug 2025 11:03:55 +0200 Subject: [PATCH 405/415] ApiListener#RelayMessageOne(): log to which Endpoint messages are relayed if they're for our parent Zone. --- lib/remote/apilistener.cpp | 19 ++++++++++++++++++- lib/remote/apilistener.hpp | 2 ++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/remote/apilistener.cpp b/lib/remote/apilistener.cpp index 547eadc7f..b2694cdaf 100644 --- a/lib/remote/apilistener.cpp +++ b/lib/remote/apilistener.cpp @@ -1269,11 +1269,12 @@ bool ApiListener::RelayMessageOne(const Zone::Ptr& targetZone, const MessageOrig ASSERT(targetZone); Zone::Ptr localZone = Zone::GetLocalZone(); + auto parentZone (localZone->GetParent()); /* only relay the message to a) the same local zone, b) the parent zone and c) direct child zones. Exception is a global zone. */ if (!targetZone->GetGlobal() && targetZone != localZone && - targetZone != localZone->GetParent() && + targetZone != parentZone && targetZone->GetParent() != localZone) { return true; } @@ -1352,12 +1353,28 @@ bool ApiListener::RelayMessageOne(const Zone::Ptr& targetZone, const MessageOrig bool isMaster = (currentZoneMaster == localEndpoint); if (!isMaster && targetEndpoint != currentZoneMaster) { + if (currentTargetZone == parentZone) { + if (m_CurrentParentEndpoint.exchange(currentZoneMaster.get()) != currentZoneMaster.get()) { + Log(LogInformation, "ApiListener") + << "Relaying messages for parent Zone '" << parentZone->GetName() + << "' through Endpoint '" << currentZoneMaster->GetName() << "' of our own Zone"; + } + } + skippedEndpoints.push_back(targetEndpoint); continue; } relayed = true; + if (currentTargetZone == parentZone) { + if (m_CurrentParentEndpoint.exchange(targetEndpoint.get()) != targetEndpoint.get()) { + Log(LogInformation, "ApiListener") + << "Relaying messages for parent Zone '" << parentZone->GetName() + << "' directly to Endpoint '" << targetEndpoint->GetName() << "' of that Zone"; + } + } + SyncSendMessage(targetEndpoint, message); } diff --git a/lib/remote/apilistener.hpp b/lib/remote/apilistener.hpp index f278c2e9b..1af1f660b 100644 --- a/lib/remote/apilistener.hpp +++ b/lib/remote/apilistener.hpp @@ -8,6 +8,7 @@ #include "remote/httpserverconnection.hpp" #include "remote/endpoint.hpp" #include "remote/messageorigin.hpp" +#include "base/atomic.hpp" #include "base/configobject.hpp" #include "base/process.hpp" #include "base/shared.hpp" @@ -187,6 +188,7 @@ private: Timer::Ptr m_RenewOwnCertTimer; Endpoint::Ptr m_LocalEndpoint; + Atomic m_CurrentParentEndpoint {nullptr}; StoppableWaitGroup::Ptr m_WaitGroup = new StoppableWaitGroup(); static ApiListener::Ptr m_Instance; From 124d191afac97c3c53bb07ee6d34dfd05a0d0efe Mon Sep 17 00:00:00 2001 From: Silas <67681686+Tqnsls@users.noreply.github.com> Date: Wed, 30 Jul 2025 15:41:38 +0200 Subject: [PATCH 406/415] add "--long-output" to ssl_cert command Co-Authored-By: Julian Brost --- doc/10-icinga-template-library.md | 1 + itl/plugins-contrib.d/web.conf | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md index a266c40fd..d96e89222 100644 --- a/doc/10-icinga-template-library.md +++ b/doc/10-icinga-template-library.md @@ -6095,6 +6095,7 @@ ssl_cert_ignore_ocsp_timeout | **Optional.** Ignore OCSP result when ssl_cert_ignore_sct | **Optional.** Do not check for signed certificate timestamps. ssl_cert_ignore_tls_renegotiation | **Optional.** Do not check for renegotiation. ssl_cert_dane | **Optional.** Verify that valid DANE records exist ({211,301,302,311,312} or empty string). +ssl_cert_long_output | **Optional.** Append the specified comma separated (no spaces) list of attributes to the plugin output on additional lines. #### jmx4perl diff --git a/itl/plugins-contrib.d/web.conf b/itl/plugins-contrib.d/web.conf index 210827c1d..84d70f1bf 100644 --- a/itl/plugins-contrib.d/web.conf +++ b/itl/plugins-contrib.d/web.conf @@ -590,8 +590,11 @@ object CheckCommand "ssl_cert" { "--ignore-maximum-validity" = { description = "Ignore the certificate maximum validity" set_if = "$ssl_cert_ignore_maximum_validity$" - } - + } + "--long-output" = { + description = "Append the specified comma separated (no spaces) list of attributes to the plugin output on additional lines" + value = "$ssl_cert_long_output$" + } } vars.ssl_cert_address = "$check_address$" From a218ba8d9228790122833a165a0a805f1ae77066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lorenz=20K=C3=A4stle?= <12514511+RincewindsHat@users.noreply.github.com> Date: Tue, 19 Aug 2025 12:58:48 +0200 Subject: [PATCH 407/415] Remove clear variable from disk CheckCommand This commit removes the -C parameter from the disk CheckCommand since there is no possible way to use it in any functional capacity. -C (or --clear) would reset the thresholds given previously to allow for setting different thresholds for following filesystmes. As an example: check_disk -w 50% -c 5% -p / -C -w 1% -p /home would only set the warning threshold for /home. Since there is no way to use it reasonably with the Icinga 2 implementation of check_disk (since thresholds can only be given once and the order is undefined), the clear flag has no worth here. My suggestion is to remove it avoid suggesting that it might be used, but I left it as a comment in the ITL to prevent the next person from "adding a missing parameter". --- doc/10-icinga-template-library.md | 1 - itl/command-plugins.conf | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/doc/10-icinga-template-library.md b/doc/10-icinga-template-library.md index d96e89222..26d1b8813 100644 --- a/doc/10-icinga-template-library.md +++ b/doc/10-icinga-template-library.md @@ -414,7 +414,6 @@ disk\_partition | **Optional.** The partition. **Deprecated in 2.3.** disk\_partition\_excluded | **Optional.** The excluded partition. **Deprecated in 2.3.** disk\_partitions | **Optional.** The partition(s). Multiple partitions must be defined as array. disk\_partitions\_excluded | **Optional.** The excluded partition(s). Multiple partitions must be defined as array. -disk\_clear | **Optional.** Clear thresholds. May be true or false. disk\_exact\_match | **Optional.** For paths or partitions specified with -p, only check for exact paths. May be true or false. disk\_errors\_only | **Optional.** Display only devices/mountpoints with errors. May be true or false. disk\_ignore\_reserved | **Optional.** If set, account root-reserved blocks are not accounted for freespace in perfdata. May be true or false. diff --git a/itl/command-plugins.conf b/itl/command-plugins.conf index 6069e0995..55cc2e4d5 100644 --- a/itl/command-plugins.conf +++ b/itl/command-plugins.conf @@ -1636,6 +1636,9 @@ object CheckCommand "disk" { command = [ PluginDir + "/check_disk" ] arguments = { + /* + "-C" (disk_clear) is missing on purpose, since there is no useful use case possible the way check_disk is mapped here + */ "--extra-opts" = { value = "$disk_extra_opts$" description = "Read extra plugin options from an ini file." @@ -1689,10 +1692,6 @@ object CheckCommand "disk" { key = "-x" value = "$disk_partition_excluded$" } - "-C" = { - set_if = "$disk_clear$" - description = "Clear thresholds" - } "-E" = { set_if = "$disk_exact_match$" description = "For paths or partitions specified with -p, only check for exact paths" From 3ebe95ba8c97745b2cef0ad0e894087a1addd3ff Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Thu, 14 Aug 2025 10:10:51 +0200 Subject: [PATCH 408/415] Allow UID/GID in ICINGA2_(USER|GROUP) environment variables --- doc/21-development.md | 6 ++-- icinga-app/icinga.cpp | 62 +++++++++++++++++++++++++------------- lib/base/utility.cpp | 70 +++++++++++++++++++++++++++++-------------- 3 files changed, 91 insertions(+), 47 deletions(-) diff --git a/doc/21-development.md b/doc/21-development.md index 2f17c8e83..c3f46fb92 100644 --- a/doc/21-development.md +++ b/doc/21-development.md @@ -2334,12 +2334,12 @@ Also see `CMakeLists.txt` for details. * `ICINGA2_CONFIGDIR`: Main config directory; defaults to `CMAKE_INSTALL_SYSCONFDIR/icinga2` usually `/etc/icinga2` * `ICINGA2_CACHEDIR`: Directory for cache files; defaults to `CMAKE_INSTALL_LOCALSTATEDIR/cache/icinga2` usually `/var/cache/icinga2` * `ICINGA2_DATADIR`: Data directory for the daemon; defaults to `CMAKE_INSTALL_LOCALSTATEDIR/lib/icinga2` usually `/var/lib/icinga2` -* `ICINGA2_LOGDIR`: Logfiles of the daemon; defaults to `CMAKE_INSTALL_LOCALSTATEDIR/log/icinga2 usually `/var/log/icinga2` +* `ICINGA2_LOGDIR`: Logfiles of the daemon; defaults to `CMAKE_INSTALL_LOCALSTATEDIR/log/icinga2` usually `/var/log/icinga2` * `ICINGA2_SPOOLDIR`: Spooling directory ; defaults to `CMAKE_INSTALL_LOCALSTATEDIR/spool/icinga2` usually `/var/spool/icinga2` * `ICINGA2_INITRUNDIR`: Runtime data for the init system; defaults to `CMAKE_INSTALL_LOCALSTATEDIR/run/icinga2` usually `/run/icinga2` * `ICINGA2_GIT_VERSION_INFO`: Whether to use Git to determine the version number; defaults to `ON` -* `ICINGA2_USER`: The user Icinga 2 should run as; defaults to `icinga` -* `ICINGA2_GROUP`: The group Icinga 2 should run as; defaults to `icinga` +* `ICINGA2_USER`: The user or user-id Icinga 2 should run as; defaults to `icinga` +* `ICINGA2_GROUP`: The group or group-id Icinga 2 should run as; defaults to `icinga` * `ICINGA2_COMMAND_GROUP`: The command group Icinga 2 should use; defaults to `icingacmd` * `ICINGA2_SYSCONFIGFILE`: Where to put the config file the initscript/systemd pulls it's dirs from; * defaults to `CMAKE_INSTALL_PREFIX/etc/sysconfig/icinga2` diff --git a/icinga-app/icinga.cpp b/icinga-app/icinga.cpp index 63b51b77d..831cba89e 100644 --- a/icinga-app/icinga.cpp +++ b/icinga-app/icinga.cpp @@ -569,42 +569,60 @@ static int Main() } else if (command && command->GetImpersonationLevel() == ImpersonateIcinga) { String group = Configuration::RunAsGroup; String user = Configuration::RunAsUser; + gid_t gid = 0; errno = 0; - struct group *gr = getgrnam(group.CStr()); - - if (!gr) { - if (errno == 0) { - Log(LogCritical, "cli") - << "Invalid group specified: " << group; - return EXIT_FAILURE; - } else { - Log(LogCritical, "cli") - << "getgrnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + try { + gid = boost::lexical_cast(group); + } catch (const boost::bad_lexical_cast&) { + struct group* gr = getgrnam(group.CStr()); + if (!gr) { + if (errno == 0) { + Log(LogCritical, "cli") + << "Invalid group specified: " << group; + } else { + Log(LogCritical, "cli") + << "getgrnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + } return EXIT_FAILURE; } + + gid = gr->gr_gid; } - if (getgid() != gr->gr_gid) { + if (getgid() != gid) { if (!vm.count("reload-internal") && setgroups(0, nullptr) < 0) { Log(LogCritical, "cli") << "setgroups() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; Log(LogCritical, "cli") - << "Please re-run this command as a privileged user or using the \"" << user << "\" account."; + << "Please rerun this command as a privileged user or using the \"" << user << "\" account."; return EXIT_FAILURE; } - if (setgid(gr->gr_gid) < 0) { + if (setgid(gid) < 0) { Log(LogCritical, "cli") << "setgid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + Log(LogCritical, "cli") + << "Please rerun this command as a privileged user or using the \"" << user << "\" account."; return EXIT_FAILURE; } } - errno = 0; - struct passwd *pw = getpwnam(user.CStr()); + std::optional uid; + struct passwd *pw = nullptr; - if (!pw) { + errno = 0; + try { + uid = boost::lexical_cast(user); + pw = getpwuid(*uid); + } catch (const boost::bad_lexical_cast&) { + pw = getpwnam(user.CStr()); + if (pw) { + uid = pw->pw_uid; + } + } + + if (!uid) { if (errno == 0) { Log(LogCritical, "cli") << "Invalid user specified: " << user; @@ -617,20 +635,22 @@ static int Main() } // also activate the additional groups the configured user is member of - if (getuid() != pw->pw_uid) { - if (!vm.count("reload-internal") && initgroups(user.CStr(), pw->pw_gid) < 0) { + if (getuid() != *uid) { + // initgroups() is only called when either getpwuid() or getpwnam() returned a valid user entry. + // Otherwise it makes no sense to set any additional groups. + if (!vm.count("reload-internal") && pw && initgroups(user.CStr(), pw->pw_gid) < 0) { Log(LogCritical, "cli") << "initgroups() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; Log(LogCritical, "cli") - << "Please re-run this command as a privileged user or using the \"" << user << "\" account."; + << "Please rerun this command as a privileged user or using the \"" << user << "\" account."; return EXIT_FAILURE; } - if (setuid(pw->pw_uid) < 0) { + if (setuid(*uid) < 0) { Log(LogCritical, "cli") << "setuid() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; Log(LogCritical, "cli") - << "Please re-run this command as a privileged user or using the \"" << user << "\" account."; + << "Please rerun this command as a privileged user or using the \"" << user << "\" account."; return EXIT_FAILURE; } } diff --git a/lib/base/utility.cpp b/lib/base/utility.cpp index 2b4e32def..1a4ac65f7 100644 --- a/lib/base/utility.cpp +++ b/lib/base/utility.cpp @@ -800,43 +800,67 @@ void Utility::RenameFile(const String& source, const String& target) #endif /* _WIN32 */ } -/* - * Set file permissions +/** + * Set the ownership of the specified file to the given user and group. + * + * In case of an error, false is returned and the error is logged. + * + * @note This operation will fail if the program is not run as root or the given user is + * not already the owner and member of the given group. + * + * @param file The path to the file as a string + * @param user Either the username or their UID as a string + * @param group Either the group's name or its GID as a string + * + * @return 'true' if the operation was successful, 'false' if an error occurred. */ bool Utility::SetFileOwnership(const String& file, const String& user, const String& group) { #ifndef _WIN32 - errno = 0; - struct passwd *pw = getpwnam(user.CStr()); + uid_t uid = 0; + try { + uid = boost::lexical_cast(user); + } catch (const boost::bad_lexical_cast&) { + errno = 0; + struct passwd* pw = getpwnam(user.CStr()); - if (!pw) { - if (errno == 0) { - Log(LogCritical, "cli") - << "Invalid user specified: " << user; - return false; - } else { - Log(LogCritical, "cli") - << "getpwnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + if (!pw) { + if (errno == 0) { + Log(LogCritical, "cli") + << "Invalid user specified: " << user; + } else { + Log(LogCritical, "cli") << "getpwnam() failed with error code " << errno << ", \"" + << Utility::FormatErrorNumber(errno) << "\""; + } return false; } + + uid = pw->pw_uid; } - errno = 0; - struct group *gr = getgrnam(group.CStr()); - if (!gr) { - if (errno == 0) { - Log(LogCritical, "cli") - << "Invalid group specified: " << group; - return false; - } else { - Log(LogCritical, "cli") - << "getgrnam() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; + gid_t gid = 0; + try { + gid = boost::lexical_cast(group); + } catch (const boost::bad_lexical_cast&) { + errno = 0; + struct group* gr = getgrnam(group.CStr()); + + if (!gr) { + if (errno == 0) { + Log(LogCritical, "cli") + << "Invalid group specified: " << group; + } else { + Log(LogCritical, "cli") << "getgrnam() failed with error code " << errno << ", \"" + << Utility::FormatErrorNumber(errno) << "\""; + } return false; } + + gid = gr->gr_gid; } - if (chown(file.CStr(), pw->pw_uid, gr->gr_gid) < 0) { + if (chown(file.CStr(), uid, gid) < 0) { Log(LogCritical, "cli") << "chown() failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\""; return false; From 37df8437006b7f515484835044f470081c646413 Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Wed, 2 Jul 2025 08:51:29 +0200 Subject: [PATCH 409/415] Add HttpRequest and HttpResponse classes --- lib/remote/CMakeLists.txt | 1 + lib/remote/httpmessage.cpp | 196 ++++++++++++++++++++++++++ lib/remote/httpmessage.hpp | 281 +++++++++++++++++++++++++++++++++++++ 3 files changed, 478 insertions(+) create mode 100644 lib/remote/httpmessage.cpp create mode 100644 lib/remote/httpmessage.hpp diff --git a/lib/remote/CMakeLists.txt b/lib/remote/CMakeLists.txt index 2271abff6..d8d3298c5 100644 --- a/lib/remote/CMakeLists.txt +++ b/lib/remote/CMakeLists.txt @@ -27,6 +27,7 @@ set(remote_SOURCES eventshandler.cpp eventshandler.hpp filterutility.cpp filterutility.hpp httphandler.cpp httphandler.hpp + httpmessage.cpp httpmessage.hpp httpserverconnection.cpp httpserverconnection.hpp httputility.cpp httputility.hpp infohandler.cpp infohandler.hpp diff --git a/lib/remote/httpmessage.cpp b/lib/remote/httpmessage.cpp new file mode 100644 index 000000000..18e5a3016 --- /dev/null +++ b/lib/remote/httpmessage.cpp @@ -0,0 +1,196 @@ +/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */ + +#include "remote/httpmessage.hpp" +#include "base/io-engine.hpp" +#include "base/json.hpp" +#include "remote/httputility.hpp" +#include "remote/url.hpp" +#include +#include +#include + +using namespace icinga; + +/** + * This is the buffer size threshold above which to flush to the connection. + * + * This value was determined with a series of measurements in + * [PR #10516](https://github.com/Icinga/icinga2/pull/10516#issuecomment-3232642284). + */ +constexpr std::size_t l_FlushThreshold = 128UL * 1024UL; + +/** + * Adapter class for Boost Beast HTTP messages body to be used with the @c JsonEncoder. + * + * This class implements the @c nlohmann::detail::output_adapter_protocol<> interface and provides + * a way to write JSON data directly into the body of a @c HttpResponse. + * + * @ingroup base + */ +class HttpResponseJsonWriter : public AsyncJsonWriter +{ +public: + explicit HttpResponseJsonWriter(HttpResponse& msg) : m_Message{msg} + { + m_Message.body().Start(); +#if BOOST_VERSION >= 107000 + // We pre-allocate more than the threshold because we always go above the threshold + // at least once. + m_Message.body().Buffer().reserve(l_FlushThreshold + (l_FlushThreshold / 4)); +#endif /* BOOST_VERSION */ + } + + ~HttpResponseJsonWriter() override { m_Message.body().Finish(); } + + void write_character(char c) override { write_characters(&c, 1); } + + void write_characters(const char* s, std::size_t length) override + { + auto buf = m_Message.body().Buffer().prepare(length); + boost::asio::buffer_copy(buf, boost::asio::const_buffer{s, length}); + m_Message.body().Buffer().commit(length); + } + + void MayFlush(boost::asio::yield_context& yield) override + { + if (m_Message.body().Size() >= l_FlushThreshold) { + m_Message.Flush(yield); + } + } + +private: + HttpResponse& m_Message; +}; + +HttpRequest::HttpRequest(Shared::Ptr stream) : m_Stream(std::move(stream)) +{ +} + +void HttpRequest::ParseHeader(boost::beast::flat_buffer& buf, boost::asio::yield_context yc) +{ + boost::beast::http::async_read_header(*m_Stream, buf, m_Parser, yc); + base() = m_Parser.get().base(); +} + +void HttpRequest::ParseBody(boost::beast::flat_buffer& buf, boost::asio::yield_context yc) +{ + boost::beast::http::async_read(*m_Stream, buf, m_Parser, yc); + body() = std::move(m_Parser.release().body()); +} + +ApiUser::Ptr HttpRequest::User() const +{ + return m_User; +} + +void HttpRequest::User(const ApiUser::Ptr& user) +{ + m_User = user; +} + +Url::Ptr HttpRequest::Url() const +{ + return m_Url; +} + +void HttpRequest::DecodeUrl() +{ + m_Url = new icinga::Url(std::string(target())); +} + +Dictionary::Ptr HttpRequest::Params() const +{ + return m_Params; +} + +void HttpRequest::DecodeParams() +{ + if (!m_Url) { + DecodeUrl(); + } + m_Params = HttpUtility::FetchRequestParameters(m_Url, body()); +} + +HttpResponse::HttpResponse(Shared::Ptr stream, HttpServerConnection::Ptr server) + : m_Server(std::move(server)), m_Stream(std::move(stream)) +{ +} + +void HttpResponse::Clear() +{ + ASSERT(!m_SerializationStarted); + boost::beast::http::response::operator=({}); +} + +void HttpResponse::Flush(boost::asio::yield_context yc) +{ + if (!chunked() && !has_content_length()) { + ASSERT(!m_SerializationStarted); + prepare_payload(); + } + + m_SerializationStarted = true; + + if (!m_Serializer.is_header_done()) { + boost::beast::http::write_header(*m_Stream, m_Serializer); + } + + boost::system::error_code ec; + boost::beast::http::async_write(*m_Stream, m_Serializer, yc[ec]); + if (ec && ec != boost::beast::http::error::need_buffer) { + if (yc.ec_) { + *yc.ec_ = ec; + return; + } + BOOST_THROW_EXCEPTION(boost::system::system_error{ec}); + } + m_Stream->async_flush(yc); + + ASSERT(m_Serializer.is_done() || !body().Finished()); +} + +void HttpResponse::StartStreaming(bool checkForDisconnect) +{ + ASSERT(body().Size() == 0 && !m_SerializationStarted); + body().Start(); + chunked(true); + + if (checkForDisconnect) { + ASSERT(m_Server); + m_Server->StartDetectClientSideShutdown(); + } +} + +bool HttpResponse::IsClientDisconnected() const +{ + ASSERT(m_Server); + return m_Server->Disconnected(); +} + +void HttpResponse::SendFile(const String& path, const boost::asio::yield_context& yc) +{ + std::ifstream fp(path.CStr(), std::ifstream::in | std::ifstream::binary | std::ifstream::ate); + fp.exceptions(std::ifstream::badbit | std::ifstream::eofbit); + + std::uint64_t remaining = fp.tellg(); + fp.seekg(0); + + content_length(remaining); + body().Start(); + + while (remaining) { + auto maxTransfer = std::min(remaining, static_cast(l_FlushThreshold)); + + auto buf = *body().Buffer().prepare(maxTransfer).begin(); + fp.read(static_cast(buf.data()), buf.size()); + body().Buffer().commit(buf.size()); + + remaining -= buf.size(); + Flush(yc); + } +} + +JsonEncoder HttpResponse::GetJsonEncoder(bool pretty) +{ + return JsonEncoder{std::make_shared(*this), pretty}; +} diff --git a/lib/remote/httpmessage.hpp b/lib/remote/httpmessage.hpp new file mode 100644 index 000000000..10d00fd49 --- /dev/null +++ b/lib/remote/httpmessage.hpp @@ -0,0 +1,281 @@ +/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */ + +#pragma once + +#include "base/dictionary.hpp" +#include "base/json.hpp" +#include "base/tlsstream.hpp" +#include "remote/apiuser.hpp" +#include "remote/httpserverconnection.hpp" +#include "remote/url.hpp" +#include +#include + +namespace icinga { + +/** + * A custom body_type for a @c boost::beast::http::message + * + * It combines the memory management of @c boost::beast::http::dynamic_body, + * which uses a multi_buffer, with the ability to continue serialization when + * new data arrives of the @c boost::beast::http::buffer_body. + * + * @tparam DynamicBuffer A buffer conforming to the boost::beast interface of the same name + * + * @ingroup remote + */ +template +struct SerializableBody +{ + class writer; + + class value_type + { + public: + template + value_type& operator<<(T&& right) + { + /* Preferably, we would return an ostream object here instead. However + * there seems to be a bug in boost::beast where if the ostream, or rather its + * streambuf object is moved into the return value, the chunked encoding gets + * mangled, leading to the client disconnecting. + * + * A workaround would have been to construct the boost::beast::detail::ostream_helper + * with the last parameter set to false, indicating that the streambuf object is not + * movable, but that is an implementation detail we'd rather not use directly in our + * code. + * + * This version has a certain overhead of the ostream being constructed on every call + * to the operator, which leads to an individual append for each time, whereas if the + * object could be kept until the entire chain of output operators is finished, only + * a single call to prepare()/commit() would have been needed. + * + * However, since this operator is mostly used for small error messages and the big + * responses are handled via a reader instance, this shouldn't be too much of a + * problem. + */ + boost::beast::ostream(m_Buffer) << std::forward(right); + return *this; + } + + [[nodiscard]] std::size_t Size() const { return m_Buffer.size(); } + + void Finish() { m_More = false; } + bool Finished() { return !m_More; } + void Start() { m_More = true; } + DynamicBuffer& Buffer() { return m_Buffer; } + + friend class writer; + + private: + /* This defaults to false so the body does not require any special handling + * for simple messages and can still be written with http::async_write(). + */ + bool m_More = false; + DynamicBuffer m_Buffer; + }; + + static std::uint64_t size(const value_type& body) { return body.Size(); } + + /** + * Implement the boost::beast BodyWriter interface for this body type + * + * This is used (for example) by the @c boost::beast::http::serializer to write out the + * message over the TLS stream. The logic is similar to the writer of the + * @c boost::beast::http::buffer_body. + * + * On the every call, it will free up the buffer range that has previously been written, + * then return a buffer containing data the has become available in the meantime. Otherwise, + * if there is more data expected in the future, for example because a corresponding reader + * has not yet finished filling the body, a `need_buffer` error is returned, to inform the + * serializer to abort writing for now, which in turn leads to the outer call to + * `http::async_write` to call their completion handlers with a `need_buffer` error, to + * notify that more data is required for another call to `http::async_write`. + */ + class writer + { + public: + using const_buffers_type = typename DynamicBuffer::const_buffers_type; + +#if BOOST_VERSION > 106600 + template + explicit writer(const boost::beast::http::header&, value_type& b) : m_Body(b) + { + } +#else + /** + * This constructor is needed specifically for boost-1.66, which was the first version + * the beast library was introduced and is still used on older (supported) distros. + */ + template + explicit writer(const boost::beast::http::message& msg) + : m_Body(const_cast(msg.body())) + { + } +#endif + void init(boost::beast::error_code& ec) { ec = {}; } + + boost::optional> get(boost::beast::error_code& ec) + { + using namespace boost::beast::http; + + if (m_SizeWritten > 0) { + m_Body.m_Buffer.consume(std::exchange(m_SizeWritten, 0)); + } + + if (m_Body.m_Buffer.size()) { + ec = {}; + m_SizeWritten = m_Body.m_Buffer.size(); + return {{m_Body.m_Buffer.data(), m_Body.m_More}}; + } + + if (m_Body.m_More) { + ec = {make_error_code(error::need_buffer)}; + } else { + ec = {}; + } + return boost::none; + } + + private: + value_type& m_Body; + std::size_t m_SizeWritten = 0; + }; +}; + +/** + * A wrapper class for a boost::beast HTTP request + * + * @ingroup remote + */ +class HttpRequest : public boost::beast::http::request +{ +public: + using ParserType = boost::beast::http::request_parser; + + explicit HttpRequest(Shared::Ptr stream); + + /** + * Parse the header of the response using the internal parser object. + * + * This first performs an @f async_read_header() into the parser, then copies + * the parsed header into this object. + */ + void ParseHeader(boost::beast::flat_buffer& buf, boost::asio::yield_context yc); + + /** + * Parse the body of the response using the internal parser object. + * + * This first performs an async_read() into the parser, then moves the parsed body + * into this object. + * + * @param buf The buffer used to track the state of the connection + * @param yc The yield_context for this operation + */ + void ParseBody(boost::beast::flat_buffer& buf, boost::asio::yield_context yc); + + ParserType& Parser() { return m_Parser; } + + [[nodiscard]] ApiUser::Ptr User() const; + void User(const ApiUser::Ptr& user); + + [[nodiscard]] icinga::Url::Ptr Url() const; + void DecodeUrl(); + + [[nodiscard]] Dictionary::Ptr Params() const; + void DecodeParams(); + +private: + ApiUser::Ptr m_User; + Url::Ptr m_Url; + Dictionary::Ptr m_Params; + + ParserType m_Parser; + + Shared::Ptr m_Stream; +}; + +/** + * A wrapper class for a boost::beast HTTP response + * + * @ingroup remote + */ +class HttpResponse : public boost::beast::http::response> +{ +public: + explicit HttpResponse(Shared::Ptr stream, HttpServerConnection::Ptr server = nullptr); + + /* Delete the base class clear() which is inherited from the fields<> class and doesn't + * clear things like the body or obviously our own members. + */ + void clear() = delete; + + /** + * Clear the header and body of the message. + * + * @note This can only be used when nothing has been written to the stream yet. + */ + void Clear(); + + /** + * Writes as much of the response as is currently available. + * + * Uses chunk-encoding if the content_length has not been set by the time this is called + * for the first time. + * + * The caller needs to ensure that the header is finished before calling this for the + * first time as changes to the header afterwards will not have any effect. + * + * @param yc The yield_context for this operation + */ + void Flush(boost::asio::yield_context yc); + + [[nodiscard]] bool HasSerializationStarted() const { return m_SerializationStarted; } + + /** + * Enables chunked encoding. + * + * Optionally starts a coroutine that reads from the stream and checks for client-side + * disconnects. In this case, the stream can not be reused after the response has been + * sent and any further requests sent over the connections will be discarded, even if + * no client-side disconnect occurs. This requires that this object has been constructed + * with a valid HttpServerConnection::Ptr. + * + * @param checkForDisconnect Whether to start a coroutine to detect disconnects + */ + void StartStreaming(bool checkForDisconnect = false); + + /** + * Check if the server has initiated a disconnect. + * + * @note This requires that the message has been constructed with a pointer to the + * @c HttpServerConnection. + */ + [[nodiscard]] bool IsClientDisconnected() const; + + /** + * Sends the contents of a file. + * + * This does not use chunked encoding because the file size is expected to be fixed. + * The message will be flushed to the stream after a certain amount has been loaded into + * the buffer. + * + * @todo Switch the implementation to @c boost::asio::stream_file when we require >=boost-1.78. + * + * @param path A path to the file + * @param yc The yield context for flushing the message. + */ + void SendFile(const String& path, const boost::asio::yield_context& yc); + + JsonEncoder GetJsonEncoder(bool pretty = false); + +private: + using Serializer = boost::beast::http::response_serializer; + Serializer m_Serializer{*this}; + bool m_SerializationStarted = false; + + HttpServerConnection::Ptr m_Server; + Shared::Ptr m_Stream; +}; + +} // namespace icinga From 3832bb4296626dfd31b31d3b56e0c41a0505d6d3 Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Wed, 23 Jul 2025 09:37:05 +0200 Subject: [PATCH 410/415] Use new HTTP message classes in HttpServerConnection and Handlers --- lib/remote/actionshandler.cpp | 10 +- lib/remote/actionshandler.hpp | 7 +- lib/remote/configfileshandler.cpp | 18 +-- lib/remote/configfileshandler.hpp | 7 +- lib/remote/configpackageshandler.cpp | 53 ++++--- lib/remote/configpackageshandler.hpp | 31 +--- lib/remote/configstageshandler.cpp | 53 ++++--- lib/remote/configstageshandler.hpp | 31 +--- lib/remote/consolehandler.cpp | 29 ++-- lib/remote/consolehandler.hpp | 17 +-- lib/remote/createobjecthandler.cpp | 11 +- lib/remote/createobjecthandler.hpp | 7 +- lib/remote/deleteobjecthandler.cpp | 11 +- lib/remote/deleteobjecthandler.hpp | 7 +- lib/remote/eventshandler.cpp | 13 +- lib/remote/eventshandler.hpp | 7 +- lib/remote/httphandler.cpp | 32 +++-- lib/remote/httphandler.hpp | 14 +- lib/remote/httpserverconnection.cpp | 203 +++++++++++++-------------- lib/remote/httpserverconnection.hpp | 4 +- lib/remote/httputility.cpp | 8 +- lib/remote/httputility.hpp | 8 +- lib/remote/infohandler.cpp | 29 ++-- lib/remote/infohandler.hpp | 7 +- lib/remote/mallocinfohandler.cpp | 14 +- lib/remote/mallocinfohandler.hpp | 7 +- lib/remote/modifyobjecthandler.cpp | 11 +- lib/remote/modifyobjecthandler.hpp | 7 +- lib/remote/objectqueryhandler.cpp | 11 +- lib/remote/objectqueryhandler.hpp | 7 +- lib/remote/statushandler.cpp | 11 +- lib/remote/statushandler.hpp | 7 +- lib/remote/templatequeryhandler.cpp | 11 +- lib/remote/templatequeryhandler.hpp | 7 +- lib/remote/typequeryhandler.cpp | 11 +- lib/remote/typequeryhandler.hpp | 7 +- lib/remote/variablequeryhandler.cpp | 11 +- lib/remote/variablequeryhandler.hpp | 7 +- 38 files changed, 325 insertions(+), 421 deletions(-) diff --git a/lib/remote/actionshandler.cpp b/lib/remote/actionshandler.cpp index 5ae5fdc80..f0fd713b1 100644 --- a/lib/remote/actionshandler.cpp +++ b/lib/remote/actionshandler.cpp @@ -18,16 +18,16 @@ REGISTER_URLHANDLER("/v1/actions", ActionsHandler); bool ActionsHandler::HandleRequest( const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) { namespace http = boost::beast::http; + auto url = request.Url(); + auto user = request.User(); + auto params = request.Params(); if (url->GetPath().size() != 3) return false; diff --git a/lib/remote/actionshandler.hpp b/lib/remote/actionshandler.hpp index fbf716797..83132eeec 100644 --- a/lib/remote/actionshandler.hpp +++ b/lib/remote/actionshandler.hpp @@ -18,11 +18,8 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) override; diff --git a/lib/remote/configfileshandler.cpp b/lib/remote/configfileshandler.cpp index 6c390e804..9a4da43ff 100644 --- a/lib/remote/configfileshandler.cpp +++ b/lib/remote/configfileshandler.cpp @@ -16,17 +16,18 @@ REGISTER_URLHANDLER("/v1/config/files", ConfigFilesHandler); bool ConfigFilesHandler::HandleRequest( const WaitGroup::Ptr&, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) { namespace http = boost::beast::http; + auto url = request.Url(); + auto user = request.User(); + auto params = request.Params(); + if (request.method() != http::verb::get) return false; @@ -78,14 +79,9 @@ bool ConfigFilesHandler::HandleRequest( } try { - std::ifstream fp(path.CStr(), std::ifstream::in | std::ifstream::binary); - fp.exceptions(std::ifstream::badbit); - - String content((std::istreambuf_iterator(fp)), std::istreambuf_iterator()); response.result(http::status::ok); response.set(http::field::content_type, "application/octet-stream"); - response.body() = content; - response.content_length(response.body().size()); + response.SendFile(path, yc); } catch (const std::exception& ex) { HttpUtility::SendJsonError(response, params, 500, "Could not read file.", DiagnosticInformation(ex)); diff --git a/lib/remote/configfileshandler.hpp b/lib/remote/configfileshandler.hpp index a8826d8c1..0bb12488d 100644 --- a/lib/remote/configfileshandler.hpp +++ b/lib/remote/configfileshandler.hpp @@ -16,11 +16,8 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) override; diff --git a/lib/remote/configpackageshandler.cpp b/lib/remote/configpackageshandler.cpp index f24f5b1d2..0f1009bfd 100644 --- a/lib/remote/configpackageshandler.cpp +++ b/lib/remote/configpackageshandler.cpp @@ -14,42 +14,41 @@ REGISTER_URLHANDLER("/v1/config/packages", ConfigPackagesHandler); bool ConfigPackagesHandler::HandleRequest( const WaitGroup::Ptr&, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) { namespace http = boost::beast::http; + auto url = request.Url(); + auto user = request.User(); + auto params = request.Params(); + if (url->GetPath().size() > 4) return false; if (request.method() == http::verb::get) - HandleGet(user, request, url, response, params); + HandleGet(request, response); else if (request.method() == http::verb::post) - HandlePost(user, request, url, response, params); + HandlePost(request, response); else if (request.method() == http::verb::delete_) - HandleDelete(user, request, url, response, params); + HandleDelete(request, response); else return false; return true; } -void ConfigPackagesHandler::HandleGet( - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params -) +void ConfigPackagesHandler::HandleGet(const HttpRequest& request, HttpResponse& response) { namespace http = boost::beast::http; + auto url = request.Url(); + auto user = request.User(); + auto params = request.Params(); + FilterUtility::CheckPermission(user, "config/query"); std::vector packages; @@ -90,16 +89,14 @@ void ConfigPackagesHandler::HandleGet( HttpUtility::SendJsonBody(response, params, result); } -void ConfigPackagesHandler::HandlePost( - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params -) +void ConfigPackagesHandler::HandlePost(const HttpRequest& request, HttpResponse& response) { namespace http = boost::beast::http; + auto url = request.Url(); + auto user = request.User(); + auto params = request.Params(); + FilterUtility::CheckPermission(user, "config/modify"); if (url->GetPath().size() >= 4) @@ -142,16 +139,14 @@ void ConfigPackagesHandler::HandlePost( HttpUtility::SendJsonBody(response, params, result); } -void ConfigPackagesHandler::HandleDelete( - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params -) +void ConfigPackagesHandler::HandleDelete(const HttpRequest& request, HttpResponse& response) { namespace http = boost::beast::http; + auto url = request.Url(); + auto user = request.User(); + auto params = request.Params(); + FilterUtility::CheckPermission(user, "config/modify"); if (url->GetPath().size() >= 4) diff --git a/lib/remote/configpackageshandler.hpp b/lib/remote/configpackageshandler.hpp index 2bae0e265..95bcfacbc 100644 --- a/lib/remote/configpackageshandler.hpp +++ b/lib/remote/configpackageshandler.hpp @@ -16,37 +16,16 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) override; private: - void HandleGet( - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params - ); - void HandlePost( - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params - ); - void HandleDelete( - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params - ); + void HandleGet(const HttpRequest& request, HttpResponse& response); + void HandlePost(const HttpRequest& request, HttpResponse& response); + void HandleDelete(const HttpRequest& request, HttpResponse& response); }; diff --git a/lib/remote/configstageshandler.cpp b/lib/remote/configstageshandler.cpp index 451ba1dbf..8ee99fbdd 100644 --- a/lib/remote/configstageshandler.cpp +++ b/lib/remote/configstageshandler.cpp @@ -21,42 +21,41 @@ static std::mutex l_RunningPackageUpdatesMutex; // Protects the above two variab bool ConfigStagesHandler::HandleRequest( const WaitGroup::Ptr&, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) { namespace http = boost::beast::http; + auto url = request.Url(); + auto user = request.User(); + auto params = request.Params(); + if (url->GetPath().size() > 5) return false; if (request.method() == http::verb::get) - HandleGet(user, request, url, response, params); + HandleGet(request, response); else if (request.method() == http::verb::post) - HandlePost(user, request, url, response, params); + HandlePost(request, response); else if (request.method() == http::verb::delete_) - HandleDelete(user, request, url, response, params); + HandleDelete(request, response); else return false; return true; } -void ConfigStagesHandler::HandleGet( - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params -) +void ConfigStagesHandler::HandleGet(const HttpRequest& request, HttpResponse& response) { namespace http = boost::beast::http; + auto url = request.Url(); + auto user = request.User(); + auto params = request.Params(); + FilterUtility::CheckPermission(user, "config/query"); if (url->GetPath().size() >= 4) @@ -95,16 +94,14 @@ void ConfigStagesHandler::HandleGet( HttpUtility::SendJsonBody(response, params, result); } -void ConfigStagesHandler::HandlePost( - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params -) +void ConfigStagesHandler::HandlePost(const HttpRequest& request, HttpResponse& response) { namespace http = boost::beast::http; + auto url = request.Url(); + auto user = request.User(); + auto params = request.Params(); + FilterUtility::CheckPermission(user, "config/modify"); if (url->GetPath().size() >= 4) @@ -208,16 +205,14 @@ void ConfigStagesHandler::HandlePost( HttpUtility::SendJsonBody(response, params, result); } -void ConfigStagesHandler::HandleDelete( - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params -) +void ConfigStagesHandler::HandleDelete(const HttpRequest& request, HttpResponse& response) { namespace http = boost::beast::http; + auto url = request.Url(); + auto user = request.User(); + auto params = request.Params(); + FilterUtility::CheckPermission(user, "config/modify"); if (url->GetPath().size() >= 4) diff --git a/lib/remote/configstageshandler.hpp b/lib/remote/configstageshandler.hpp index a6abb726d..f49c2efb1 100644 --- a/lib/remote/configstageshandler.hpp +++ b/lib/remote/configstageshandler.hpp @@ -16,37 +16,16 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) override; private: - void HandleGet( - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params - ); - void HandlePost( - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params - ); - void HandleDelete( - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params - ); + void HandleGet(const HttpRequest& request, HttpResponse& response); + void HandlePost(const HttpRequest& request, HttpResponse& response); + void HandleDelete(const HttpRequest& request, HttpResponse& response); }; } diff --git a/lib/remote/consolehandler.cpp b/lib/remote/consolehandler.cpp index c48821aae..c063e5781 100644 --- a/lib/remote/consolehandler.cpp +++ b/lib/remote/consolehandler.cpp @@ -56,17 +56,18 @@ static void EnsureFrameCleanupTimer() bool ConsoleHandler::HandleRequest( const WaitGroup::Ptr&, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) { namespace http = boost::beast::http; + auto url = request.Url(); + auto user = request.User(); + auto params = request.Params(); + if (url->GetPath().size() != 3) return false; @@ -96,17 +97,16 @@ bool ConsoleHandler::HandleRequest( } if (methodName == "execute-script") - return ExecuteScriptHelper(request, response, params, command, session, sandboxed); + return ExecuteScriptHelper(request, response, command, session, sandboxed); else if (methodName == "auto-complete-script") - return AutocompleteScriptHelper(request, response, params, command, session, sandboxed); + return AutocompleteScriptHelper(request, response, command, session, sandboxed); HttpUtility::SendJsonError(response, params, 400, "Invalid method specified: " + methodName); return true; } -bool ConsoleHandler::ExecuteScriptHelper(boost::beast::http::request& request, - boost::beast::http::response& response, - const Dictionary::Ptr& params, const String& command, const String& session, bool sandboxed) +bool ConsoleHandler::ExecuteScriptHelper(const HttpRequest& request, HttpResponse& response, + const String& command, const String& session, bool sandboxed) { namespace http = boost::beast::http; @@ -174,14 +174,13 @@ bool ConsoleHandler::ExecuteScriptHelper(boost::beast::http::request& request, - boost::beast::http::response& response, - const Dictionary::Ptr& params, const String& command, const String& session, bool sandboxed) +bool ConsoleHandler::AutocompleteScriptHelper(const HttpRequest& request, HttpResponse& response, + const String& command, const String& session, bool sandboxed) { namespace http = boost::beast::http; @@ -213,7 +212,7 @@ bool ConsoleHandler::AutocompleteScriptHelper(boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) override; @@ -37,12 +34,10 @@ public: static std::vector GetAutocompletionSuggestions(const String& word, ScriptFrame& frame); private: - static bool ExecuteScriptHelper(boost::beast::http::request& request, - boost::beast::http::response& response, - const Dictionary::Ptr& params, const String& command, const String& session, bool sandboxed); - static bool AutocompleteScriptHelper(boost::beast::http::request& request, - boost::beast::http::response& response, - const Dictionary::Ptr& params, const String& command, const String& session, bool sandboxed); + static bool ExecuteScriptHelper(const HttpRequest& request, HttpResponse& response, + const String& command, const String& session, bool sandboxed); + static bool AutocompleteScriptHelper(const HttpRequest& request, HttpResponse& response, + const String& command, const String& session, bool sandboxed); }; diff --git a/lib/remote/createobjecthandler.cpp b/lib/remote/createobjecthandler.cpp index 119be1cd9..447b74c6d 100644 --- a/lib/remote/createobjecthandler.cpp +++ b/lib/remote/createobjecthandler.cpp @@ -18,17 +18,18 @@ REGISTER_URLHANDLER("/v1/objects", CreateObjectHandler); bool CreateObjectHandler::HandleRequest( const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) { namespace http = boost::beast::http; + auto url = request.Url(); + auto user = request.User(); + auto params = request.Params(); + if (url->GetPath().size() != 4) return false; diff --git a/lib/remote/createobjecthandler.hpp b/lib/remote/createobjecthandler.hpp index 3f6a705c2..317cf023c 100644 --- a/lib/remote/createobjecthandler.hpp +++ b/lib/remote/createobjecthandler.hpp @@ -16,11 +16,8 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) override; diff --git a/lib/remote/deleteobjecthandler.cpp b/lib/remote/deleteobjecthandler.cpp index 54d31f13d..d0f49f83c 100644 --- a/lib/remote/deleteobjecthandler.cpp +++ b/lib/remote/deleteobjecthandler.cpp @@ -18,17 +18,18 @@ REGISTER_URLHANDLER("/v1/objects", DeleteObjectHandler); bool DeleteObjectHandler::HandleRequest( const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) { namespace http = boost::beast::http; + auto url = request.Url(); + auto user = request.User(); + auto params = request.Params(); + if (url->GetPath().size() < 3 || url->GetPath().size() > 4) return false; diff --git a/lib/remote/deleteobjecthandler.hpp b/lib/remote/deleteobjecthandler.hpp index 0f9643277..076f76704 100644 --- a/lib/remote/deleteobjecthandler.hpp +++ b/lib/remote/deleteobjecthandler.hpp @@ -16,11 +16,8 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) override; diff --git a/lib/remote/eventshandler.cpp b/lib/remote/eventshandler.cpp index 2cbee92f3..813d5f41e 100644 --- a/lib/remote/eventshandler.cpp +++ b/lib/remote/eventshandler.cpp @@ -42,11 +42,8 @@ const String l_ApiQuery (""); bool EventsHandler::HandleRequest( const WaitGroup::Ptr&, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) @@ -54,6 +51,10 @@ bool EventsHandler::HandleRequest( namespace asio = boost::asio; namespace http = boost::beast::http; + auto url = request.Url(); + auto user = request.User(); + auto params = request.Params(); + if (url->GetPath().size() != 2) return false; @@ -101,7 +102,7 @@ bool EventsHandler::HandleRequest( EventsSubscriber subscriber (std::move(eventTypes), HttpUtility::GetLastParameter(params, "filter"), l_ApiQuery); - server.StartStreaming(); + server.StartDetectClientSideShutdown(); response.result(http::status::ok); response.set(http::field::content_type, "application/json"); diff --git a/lib/remote/eventshandler.hpp b/lib/remote/eventshandler.hpp index 49229733a..68a1f9844 100644 --- a/lib/remote/eventshandler.hpp +++ b/lib/remote/eventshandler.hpp @@ -17,11 +17,8 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) override; diff --git a/lib/remote/httphandler.cpp b/lib/remote/httphandler.cpp index 79571d760..b6d8d0f4b 100644 --- a/lib/remote/httphandler.cpp +++ b/lib/remote/httphandler.cpp @@ -49,9 +49,8 @@ void HttpHandler::Register(const Url::Ptr& url, const HttpHandler::Ptr& handler) void HttpHandler::ProcessRequest( const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - boost::beast::http::response& response, + HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) @@ -59,8 +58,8 @@ void HttpHandler::ProcessRequest( Dictionary::Ptr node = m_UrlTree; std::vector handlers; - Url::Ptr url = new Url(std::string(request.target())); - auto& path (url->GetPath()); + request.DecodeUrl(); + auto& path (request.Url()->GetPath()); for (std::vector::size_type i = 0; i <= path.size(); i++) { Array::Ptr current_handlers = node->Get("handlers"); @@ -90,12 +89,10 @@ void HttpHandler::ProcessRequest( std::reverse(handlers.begin(), handlers.end()); - Dictionary::Ptr params; - try { - params = HttpUtility::FetchRequestParameters(url, request.body()); + request.DecodeParams(); } catch (const std::exception& ex) { - HttpUtility::SendJsonError(response, params, 400, "Invalid request body: " + DiagnosticInformation(ex, false)); + HttpUtility::SendJsonError(response, request.Params(), 400, "Invalid request body: " + DiagnosticInformation(ex, false)); return; } @@ -109,12 +106,25 @@ void HttpHandler::ProcessRequest( */ try { for (const HttpHandler::Ptr& handler : handlers) { - if (handler->HandleRequest(waitGroup, stream, user, request, url, response, params, yc, server)) { + if (handler->HandleRequest(waitGroup, stream, request, response, yc, server)) { processed = true; break; } } } catch (const std::exception& ex) { + // Errors related to writing the response should be handled in HttpServerConnection. + if (dynamic_cast(&ex)) { + throw; + } + + /* This means we can't send an error response because the exception was thrown + * in the middle of a streaming response. We can't send any error response, so the + * only thing we can do is propagate it up. + */ + if (response.HasSerializationStarted()) { + throw; + } + Log(LogWarning, "HttpServerConnection") << "Error while processing HTTP request: " << ex.what(); @@ -122,7 +132,7 @@ void HttpHandler::ProcessRequest( } if (!processed) { - HttpUtility::SendJsonError(response, params, 404, "The requested path '" + boost::algorithm::join(path, "/") + + HttpUtility::SendJsonError(response, request.Params(), 404, "The requested path '" + boost::algorithm::join(path, "/") + "' could not be found or the request method is not valid for this path."); return; } diff --git a/lib/remote/httphandler.hpp b/lib/remote/httphandler.hpp index ec67ae8a4..0d6bd12b8 100644 --- a/lib/remote/httphandler.hpp +++ b/lib/remote/httphandler.hpp @@ -4,8 +4,10 @@ #define HTTPHANDLER_H #include "remote/i2-remote.hpp" +#include "base/io-engine.hpp" #include "remote/url.hpp" #include "remote/httpserverconnection.hpp" +#include "remote/httpmessage.hpp" #include "remote/apiuser.hpp" #include "base/registry.hpp" #include "base/tlsstream.hpp" @@ -29,11 +31,8 @@ public: virtual bool HandleRequest( const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) = 0; @@ -42,9 +41,8 @@ public: static void ProcessRequest( const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - boost::beast::http::response& response, + HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ); diff --git a/lib/remote/httpserverconnection.cpp b/lib/remote/httpserverconnection.cpp index 17e61f160..cd4ca367b 100644 --- a/lib/remote/httpserverconnection.cpp +++ b/lib/remote/httpserverconnection.cpp @@ -41,8 +41,7 @@ HttpServerConnection::HttpServerConnection(const WaitGroup::Ptr& waitGroup, cons } HttpServerConnection::HttpServerConnection(const WaitGroup::Ptr& waitGroup, const String& identity, bool authenticated, const Shared::Ptr& stream, boost::asio::io_context& io) - : m_WaitGroup(waitGroup), m_Stream(stream), m_Seen(Utility::GetTime()), m_IoStrand(io), m_ShuttingDown(false), m_HasStartedStreaming(false), - m_CheckLivenessTimer(io) + : m_WaitGroup(waitGroup), m_Stream(stream), m_Seen(Utility::GetTime()), m_IoStrand(io), m_ShuttingDown(false), m_ConnectionReusable(true), m_CheckLivenessTimer(io) { if (authenticated) { m_ApiUser = ApiUser::GetByClientCN(identity); @@ -99,14 +98,40 @@ void HttpServerConnection::Disconnect(boost::asio::yield_context yc) } } -void HttpServerConnection::StartStreaming() +/** + * Starts a coroutine that continually reads from the stream to detect a disconnect from the client. + * + * This can be accessed inside an @c HttpHandler via the HttpResponse::StartStreaming() method by + * passing true as the argument, expressing that disconnect detection is desired. + */ +void HttpServerConnection::StartDetectClientSideShutdown() { namespace asio = boost::asio; - m_HasStartedStreaming = true; + m_ConnectionReusable = false; HttpServerConnection::Ptr keepAlive (this); + /* Technically it would be possible to detect disconnects on the TCP-side by setting the + * socket to non-blocking and then performing a read directly on the socket with the message_peek + * flag. As the TCP FIN message will put the connection into a CLOSE_WAIT even if the kernel + * buffer is full, this would technically be reliable way of detecting a shutdown and free + * of side-effects. + * + * However, for detecting the close_notify on the SSL/TLS-side, an async_fill() would be necessary + * when the check on the TCP level above returns that there are readable bytes (and no FIN/eof). + * If this async_fill() then buffers more application data and not an immediate eof, we could + * attempt to read another message before disconnecting. + * + * This could either be done at the level of the handlers, via the @c HttpResponse class, or + * generally as a separate coroutine here in @c HttpServerConnection, both (mostly) side-effect + * free and without affecting the state of the connection. + * + * However, due to the complexity of this approach, involving several asio operations, message + * flags, synchronous and asynchronous operations in blocking and non-blocking mode, ioctl cmds, + * etc., it was decided to stick with a simple reading loop, started conditionally on request by + * the handler. + */ IoEngine::SpawnCoroutine(m_IoStrand, [this, keepAlive](asio::yield_context yc) { if (!m_ShuttingDown) { char buf[128]; @@ -129,10 +154,9 @@ bool HttpServerConnection::Disconnected() static inline bool EnsureValidHeaders( - AsioTlsStream& stream, boost::beast::flat_buffer& buf, - boost::beast::http::parser& parser, - boost::beast::http::response& response, + HttpRequest& request, + HttpResponse& response, bool& shuttingDown, boost::asio::yield_context& yc ) @@ -147,7 +171,7 @@ bool EnsureValidHeaders( boost::system::error_code ec; - http::async_read_header(stream, buf, parser, yc[ec]); + request.ParseHeader(buf, yc[ec]); if (ec) { if (ec == boost::asio::error::operation_aborted) @@ -156,7 +180,7 @@ bool EnsureValidHeaders( errorMsg = ec.message(); httpError = true; } else { - switch (parser.get().version()) { + switch (request.version()) { case 10: case 11: break; @@ -168,21 +192,16 @@ bool EnsureValidHeaders( if (!errorMsg.IsEmpty() || httpError) { response.result(http::status::bad_request); - if (!httpError && parser.get()[http::field::accept] == "application/json") { - HttpUtility::SendJsonBody(response, nullptr, new Dictionary({ - { "error", 400 }, - { "status", String("Bad Request: ") + errorMsg } - })); + if (!httpError && request[http::field::accept] == "application/json") { + HttpUtility::SendJsonError(response, nullptr, 400, "Bad Request: " + errorMsg); } else { response.set(http::field::content_type, "text/html"); - response.body() = String("

    Bad Request

    ") + errorMsg + "

    "; - response.content_length(response.body().size()); + response.body() << "

    Bad Request

    " << errorMsg << "

    "; } response.set(http::field::connection, "close"); - http::async_write(stream, response, yc); - stream.async_flush(yc); + response.Flush(yc); return false; } @@ -192,28 +211,24 @@ bool EnsureValidHeaders( static inline void HandleExpect100( - AsioTlsStream& stream, - boost::beast::http::request& request, + const Shared::Ptr& stream, + const HttpRequest& request, boost::asio::yield_context& yc ) { namespace http = boost::beast::http; if (request[http::field::expect] == "100-continue") { - http::response response; - + HttpResponse response{stream}; response.result(http::status::continue_); - - http::async_write(stream, response, yc); - stream.async_flush(yc); + response.Flush(yc); } } static inline bool HandleAccessControl( - AsioTlsStream& stream, - boost::beast::http::request& request, - boost::beast::http::response& response, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc ) { @@ -240,12 +255,10 @@ bool HandleAccessControl( response.result(http::status::ok); response.set(http::field::access_control_allow_methods, "GET, POST, PUT, DELETE"); response.set(http::field::access_control_allow_headers, "Authorization, Content-Type, X-HTTP-Method-Override"); - response.body() = "Preflight OK"; - response.content_length(response.body().size()); + response.body() << "Preflight OK"; response.set(http::field::connection, "close"); - http::async_write(stream, response, yc); - stream.async_flush(yc); + response.Flush(yc); return false; } @@ -258,9 +271,8 @@ bool HandleAccessControl( static inline bool EnsureAcceptHeader( - AsioTlsStream& stream, - boost::beast::http::request& request, - boost::beast::http::response& response, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc ) { @@ -269,12 +281,10 @@ bool EnsureAcceptHeader( if (request.method() != http::verb::get && request[http::field::accept] != "application/json") { response.result(http::status::bad_request); response.set(http::field::content_type, "text/html"); - response.body() = "

    Accept header is missing or not set to 'application/json'.

    "; - response.content_length(response.body().size()); + response.body() << "

    Accept header is missing or not set to 'application/json'.

    "; response.set(http::field::connection, "close"); - http::async_write(stream, response, yc); - stream.async_flush(yc); + response.Flush(yc); return false; } @@ -284,16 +294,14 @@ bool EnsureAcceptHeader( static inline bool EnsureAuthenticatedUser( - AsioTlsStream& stream, - boost::beast::http::request& request, - ApiUser::Ptr& authenticatedUser, - boost::beast::http::response& response, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc ) { namespace http = boost::beast::http; - if (!authenticatedUser) { + if (!request.User()) { Log(LogWarning, "HttpServerConnection") << "Unauthorized request: " << request.method_string() << ' ' << request.target(); @@ -302,18 +310,13 @@ bool EnsureAuthenticatedUser( response.set(http::field::connection, "close"); if (request[http::field::accept] == "application/json") { - HttpUtility::SendJsonBody(response, nullptr, new Dictionary({ - { "error", 401 }, - { "status", "Unauthorized. Please check your user credentials." } - })); + HttpUtility::SendJsonError(response, nullptr, 401, "Unauthorized. Please check your user credentials."); } else { response.set(http::field::content_type, "text/html"); - response.body() = "

    Unauthorized. Please check your user credentials.

    "; - response.content_length(response.body().size()); + response.body() << "

    Unauthorized. Please check your user credentials.

    "; } - http::async_write(stream, response, yc); - stream.async_flush(yc); + response.Flush(yc); return false; } @@ -323,11 +326,9 @@ bool EnsureAuthenticatedUser( static inline bool EnsureValidBody( - AsioTlsStream& stream, boost::beast::flat_buffer& buf, - boost::beast::http::parser& parser, - ApiUser::Ptr& authenticatedUser, - boost::beast::http::response& response, + HttpRequest& request, + HttpResponse& response, bool& shuttingDown, boost::asio::yield_context& yc ) @@ -336,7 +337,7 @@ bool EnsureValidBody( { size_t maxSize = 1024 * 1024; - Array::Ptr permissions = authenticatedUser->GetPermissions(); + Array::Ptr permissions = request.User()->GetPermissions(); if (permissions) { ObjectLock olock(permissions); @@ -366,7 +367,7 @@ bool EnsureValidBody( } } - parser.body_limit(maxSize); + request.Parser().body_limit(maxSize); } if (shuttingDown) @@ -374,7 +375,7 @@ bool EnsureValidBody( boost::system::error_code ec; - http::async_read(stream, buf, parser, yc[ec]); + request.ParseBody(buf, yc[ec]); if (ec) { if (ec == boost::asio::error::operation_aborted) @@ -389,21 +390,16 @@ bool EnsureValidBody( response.result(http::status::bad_request); - if (parser.get()[http::field::accept] == "application/json") { - HttpUtility::SendJsonBody(response, nullptr, new Dictionary({ - { "error", 400 }, - { "status", String("Bad Request: ") + ec.message() } - })); + if (request[http::field::accept] == "application/json") { + HttpUtility::SendJsonError(response, nullptr, 400, "Bad Request: " + ec.message()); } else { response.set(http::field::content_type, "text/html"); - response.body() = String("

    Bad Request

    ") + ec.message() + "

    "; - response.content_length(response.body().size()); + response.body() << "

    Bad Request

    " << ec.message() << "

    "; } response.set(http::field::connection, "close"); - http::async_write(stream, response, yc); - stream.async_flush(yc); + response.Flush(yc); return false; } @@ -414,52 +410,45 @@ bool EnsureValidBody( static inline bool ProcessRequest( AsioTlsStream& stream, - boost::beast::http::request& request, - ApiUser::Ptr& authenticatedUser, - boost::beast::http::response& response, + HttpRequest& request, + HttpResponse& response, HttpServerConnection& server, - bool& hasStartedStreaming, + bool& connectionReusable, const WaitGroup::Ptr& waitGroup, std::chrono::steady_clock::duration& cpuBoundWorkTime, boost::asio::yield_context& yc ) { - namespace http = boost::beast::http; - try { // Cache the elapsed time to acquire a CPU semaphore used to detect extremely heavy workloads. auto start (std::chrono::steady_clock::now()); CpuBoundWork handlingRequest (yc); cpuBoundWorkTime = std::chrono::steady_clock::now() - start; - HttpHandler::ProcessRequest(waitGroup, stream, authenticatedUser, request, response, yc, server); + HttpHandler::ProcessRequest(waitGroup, stream, request, response, yc, server); } catch (const std::exception& ex) { - if (hasStartedStreaming) { + if (!connectionReusable) { return false; } - auto sysErr (dynamic_cast(&ex)); - - if (sysErr && sysErr->code() == boost::asio::error::operation_aborted) { + /* Since we don't know the state the stream is in, we can't send an error response and + * have to just cause a disconnect here. + */ + if (response.HasSerializationStarted()) { throw; } - http::response response; - - HttpUtility::SendJsonError(response, nullptr, 500, "Unhandled exception" , DiagnosticInformation(ex)); - - http::async_write(stream, response, yc); - stream.async_flush(yc); - + HttpUtility::SendJsonError(response, request.Params(), 500, "Unhandled exception", DiagnosticInformation(ex)); + response.Flush(yc); return true; } - if (hasStartedStreaming) { + if (!connectionReusable) { return false; } - http::async_write(stream, response, yc); - stream.async_flush(yc); + response.body().Finish(); + response.Flush(yc); return true; } @@ -481,23 +470,21 @@ void HttpServerConnection::ProcessMessages(boost::asio::yield_context yc) while (m_WaitGroup->IsLockable()) { m_Seen = Utility::GetTime(); - http::parser parser; - http::response response; + HttpRequest request(m_Stream); + HttpResponse response(m_Stream, this); - parser.header_limit(1024 * 1024); - parser.body_limit(-1); + request.Parser().header_limit(1024 * 1024); + request.Parser().body_limit(-1); response.set(http::field::server, l_ServerHeader); - if (!EnsureValidHeaders(*m_Stream, buf, parser, response, m_ShuttingDown, yc)) { + if (!EnsureValidHeaders(buf, request, response, m_ShuttingDown, yc)) { break; } m_Seen = Utility::GetTime(); auto start (ch::steady_clock::now()); - auto& request (parser.get()); - { auto method (http::string_to_verb(request["X-Http-Method-Override"])); @@ -506,19 +493,19 @@ void HttpServerConnection::ProcessMessages(boost::asio::yield_context yc) } } - HandleExpect100(*m_Stream, request, yc); + HandleExpect100(m_Stream, request, yc); - auto authenticatedUser (m_ApiUser); - - if (!authenticatedUser) { - authenticatedUser = ApiUser::GetByAuthHeader(std::string(request[http::field::authorization])); + if (m_ApiUser) { + request.User(m_ApiUser); + } else { + request.User(ApiUser::GetByAuthHeader(std::string(request[http::field::authorization]))); } Log logMsg (LogInformation, "HttpServerConnection"); logMsg << "Request " << request.method_string() << ' ' << request.target() << " (from " << m_PeerAddress - << ", user: " << (authenticatedUser ? authenticatedUser->GetName() : "") + << ", user: " << (request.User() ? request.User()->GetName() : "") << ", agent: " << request[http::field::user_agent]; //operator[] - Returns the value for a field, or "" if it does not exist. ch::steady_clock::duration cpuBoundWorkTime(0); @@ -531,29 +518,29 @@ void HttpServerConnection::ProcessMessages(boost::asio::yield_context yc) logMsg << " took total " << ch::duration_cast(ch::steady_clock::now() - start).count() << "ms."; }); - if (!HandleAccessControl(*m_Stream, request, response, yc)) { + if (!HandleAccessControl(request, response, yc)) { break; } - if (!EnsureAcceptHeader(*m_Stream, request, response, yc)) { + if (!EnsureAcceptHeader(request, response, yc)) { break; } - if (!EnsureAuthenticatedUser(*m_Stream, request, authenticatedUser, response, yc)) { + if (!EnsureAuthenticatedUser(request, response, yc)) { break; } - if (!EnsureValidBody(*m_Stream, buf, parser, authenticatedUser, response, m_ShuttingDown, yc)) { + if (!EnsureValidBody(buf, request, response, m_ShuttingDown, yc)) { break; } m_Seen = std::numeric_limits::max(); - if (!ProcessRequest(*m_Stream, request, authenticatedUser, response, *this, m_HasStartedStreaming, m_WaitGroup, cpuBoundWorkTime, yc)) { + if (!ProcessRequest(*m_Stream, request, response, *this, m_ConnectionReusable, m_WaitGroup, cpuBoundWorkTime, yc)) { break; } - if (request.version() != 11 || request[http::field::connection] == "close") { + if (!request.keep_alive() || !m_ConnectionReusable) { break; } } diff --git a/lib/remote/httpserverconnection.hpp b/lib/remote/httpserverconnection.hpp index e4f7d257e..1f3d5d7f9 100644 --- a/lib/remote/httpserverconnection.hpp +++ b/lib/remote/httpserverconnection.hpp @@ -30,7 +30,7 @@ public: const Shared::Ptr& stream); void Start(); - void StartStreaming(); + void StartDetectClientSideShutdown(); bool Disconnected(); private: @@ -41,7 +41,7 @@ private: String m_PeerAddress; boost::asio::io_context::strand m_IoStrand; bool m_ShuttingDown; - bool m_HasStartedStreaming; + bool m_ConnectionReusable; boost::asio::deadline_timer m_CheckLivenessTimer; HttpServerConnection(const WaitGroup::Ptr& waitGroup, const String& identity, bool authenticated, diff --git a/lib/remote/httputility.cpp b/lib/remote/httputility.cpp index a2142e5d8..b53a8721b 100644 --- a/lib/remote/httputility.cpp +++ b/lib/remote/httputility.cpp @@ -52,16 +52,15 @@ Value HttpUtility::GetLastParameter(const Dictionary::Ptr& params, const String& return arr->Get(arr->GetLength() - 1); } -void HttpUtility::SendJsonBody(boost::beast::http::response& response, const Dictionary::Ptr& params, const Value& val) +void HttpUtility::SendJsonBody(HttpResponse& response, const Dictionary::Ptr& params, const Value& val) { namespace http = boost::beast::http; response.set(http::field::content_type, "application/json"); - response.body() = JsonEncode(val, params && GetLastParameter(params, "pretty")); - response.content_length(response.body().size()); + response.GetJsonEncoder(params && GetLastParameter(params, "pretty")).Encode(val); } -void HttpUtility::SendJsonError(boost::beast::http::response& response, +void HttpUtility::SendJsonError(HttpResponse& response, const Dictionary::Ptr& params, int code, const String& info, const String& diagnosticInformation) { Dictionary::Ptr result = new Dictionary({ { "error", code } }); @@ -74,6 +73,7 @@ void HttpUtility::SendJsonError(boost::beast::http::responseSet("diagnostic_information", diagnosticInformation); } + response.Clear(); response.result(code); HttpUtility::SendJsonBody(response, params, result); diff --git a/lib/remote/httputility.hpp b/lib/remote/httputility.hpp index 6465b4af9..6f6427713 100644 --- a/lib/remote/httputility.hpp +++ b/lib/remote/httputility.hpp @@ -5,7 +5,7 @@ #include "remote/url.hpp" #include "base/dictionary.hpp" -#include +#include "remote/httpmessage.hpp" #include namespace icinga @@ -23,9 +23,9 @@ public: static Dictionary::Ptr FetchRequestParameters(const Url::Ptr& url, const std::string& body); static Value GetLastParameter(const Dictionary::Ptr& params, const String& key); - static void SendJsonBody(boost::beast::http::response& response, const Dictionary::Ptr& params, const Value& val); - static void SendJsonError(boost::beast::http::response& response, const Dictionary::Ptr& params, const int code, - const String& verbose = String(), const String& diagnosticInformation = String()); + static void SendJsonBody(HttpResponse& response, const Dictionary::Ptr& params, const Value& val); + static void SendJsonError(HttpResponse& response, const Dictionary::Ptr& params, const int code, + const String& info = {}, const String& diagnosticInformation = {}); }; } diff --git a/lib/remote/infohandler.cpp b/lib/remote/infohandler.cpp index 5fc621cd8..9363f7ca0 100644 --- a/lib/remote/infohandler.cpp +++ b/lib/remote/infohandler.cpp @@ -11,17 +11,18 @@ REGISTER_URLHANDLER("/", InfoHandler); bool InfoHandler::HandleRequest( const WaitGroup::Ptr&, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) { namespace http = boost::beast::http; + auto url = request.Url(); + auto user = request.User(); + auto params = request.Params(); + if (url->GetPath().size() > 2) return false; @@ -77,23 +78,23 @@ bool InfoHandler::HandleRequest( } else { response.set(http::field::content_type, "text/html"); - String body = "Icinga 2

    Hello from Icinga 2 (Version: " + Application::GetAppVersion() + ")!

    "; - body += "

    You are authenticated as " + user->GetName() + ". "; + auto& body = response.body(); + body << "Icinga 2

    Hello from Icinga 2 (Version: " + << Application::GetAppVersion() << ")!

    " + << "

    You are authenticated as " << user->GetName() << ". "; if (!permInfo.empty()) { - body += "Your user has the following permissions:

      "; + body << "Your user has the following permissions:

        "; for (const String& perm : permInfo) { - body += "
      • " + perm + "
      • "; + body << "
      • " << perm << "
      • "; } - body += "
      "; + body << "
    "; } else - body += "Your user does not have any permissions.

    "; + body << "Your user does not have any permissions.

    "; - body += R"(

    More information about API requests is available in the documentation.

    )"; - response.body() = body; - response.content_length(response.body().size()); + body << R"(

    More information about API requests is available in the documentation.

    )"; } return true; diff --git a/lib/remote/infohandler.hpp b/lib/remote/infohandler.hpp index 7396f5ac9..f0f6499a3 100644 --- a/lib/remote/infohandler.hpp +++ b/lib/remote/infohandler.hpp @@ -16,11 +16,8 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) override; diff --git a/lib/remote/mallocinfohandler.cpp b/lib/remote/mallocinfohandler.cpp index f4c27cac4..465b47b86 100644 --- a/lib/remote/mallocinfohandler.cpp +++ b/lib/remote/mallocinfohandler.cpp @@ -20,17 +20,18 @@ REGISTER_URLHANDLER("/v1/debug/malloc_info", MallocInfoHandler); bool MallocInfoHandler::HandleRequest( const WaitGroup::Ptr&, AsioTlsStream&, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context&, HttpServerConnection& ) { namespace http = boost::beast::http; + auto url = request.Url(); + auto user = request.User(); + auto params = request.Params(); + if (url->GetPath().size() != 3) { return false; } @@ -87,8 +88,7 @@ bool MallocInfoHandler::HandleRequest( response.result(200); response.set(http::field::content_type, "application/xml"); - response.body() = std::string(buf, bufSize); - response.content_length(response.body().size()); + response.body() << std::string_view(buf, bufSize); #endif /* HAVE_MALLOC_INFO */ return true; diff --git a/lib/remote/mallocinfohandler.hpp b/lib/remote/mallocinfohandler.hpp index 9648fac9f..fc32341fa 100644 --- a/lib/remote/mallocinfohandler.hpp +++ b/lib/remote/mallocinfohandler.hpp @@ -15,11 +15,8 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) override; diff --git a/lib/remote/modifyobjecthandler.cpp b/lib/remote/modifyobjecthandler.cpp index c71be6a9a..4b9157af8 100644 --- a/lib/remote/modifyobjecthandler.cpp +++ b/lib/remote/modifyobjecthandler.cpp @@ -16,17 +16,18 @@ REGISTER_URLHANDLER("/v1/objects", ModifyObjectHandler); bool ModifyObjectHandler::HandleRequest( const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) { namespace http = boost::beast::http; + auto url = request.Url(); + auto user = request.User(); + auto params = request.Params(); + if (url->GetPath().size() < 3 || url->GetPath().size() > 4) return false; diff --git a/lib/remote/modifyobjecthandler.hpp b/lib/remote/modifyobjecthandler.hpp index f299acd6e..32ddf176c 100644 --- a/lib/remote/modifyobjecthandler.hpp +++ b/lib/remote/modifyobjecthandler.hpp @@ -16,11 +16,8 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) override; diff --git a/lib/remote/objectqueryhandler.cpp b/lib/remote/objectqueryhandler.cpp index f6f049e4e..edc282453 100644 --- a/lib/remote/objectqueryhandler.cpp +++ b/lib/remote/objectqueryhandler.cpp @@ -91,17 +91,18 @@ Dictionary::Ptr ObjectQueryHandler::SerializeObjectAttrs(const Object::Ptr& obje bool ObjectQueryHandler::HandleRequest( const WaitGroup::Ptr&, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) { namespace http = boost::beast::http; + auto url = request.Url(); + auto user = request.User(); + auto params = request.Params(); + if (url->GetPath().size() < 3 || url->GetPath().size() > 4) return false; diff --git a/lib/remote/objectqueryhandler.hpp b/lib/remote/objectqueryhandler.hpp index 376eb661e..d26a9e1ca 100644 --- a/lib/remote/objectqueryhandler.hpp +++ b/lib/remote/objectqueryhandler.hpp @@ -16,11 +16,8 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) override; diff --git a/lib/remote/statushandler.cpp b/lib/remote/statushandler.cpp index bf14152f8..9c597dd98 100644 --- a/lib/remote/statushandler.cpp +++ b/lib/remote/statushandler.cpp @@ -71,17 +71,18 @@ public: bool StatusHandler::HandleRequest( const WaitGroup::Ptr&, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) { namespace http = boost::beast::http; + auto url = request.Url(); + auto user = request.User(); + auto params = request.Params(); + if (url->GetPath().size() > 3) return false; diff --git a/lib/remote/statushandler.hpp b/lib/remote/statushandler.hpp index 109fd4881..1d05347d1 100644 --- a/lib/remote/statushandler.hpp +++ b/lib/remote/statushandler.hpp @@ -16,11 +16,8 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) override; diff --git a/lib/remote/templatequeryhandler.cpp b/lib/remote/templatequeryhandler.cpp index a68ad6dad..9dceabb7b 100644 --- a/lib/remote/templatequeryhandler.cpp +++ b/lib/remote/templatequeryhandler.cpp @@ -78,17 +78,18 @@ public: bool TemplateQueryHandler::HandleRequest( const WaitGroup::Ptr&, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) { namespace http = boost::beast::http; + auto url = request.Url(); + auto user = request.User(); + auto params = request.Params(); + if (url->GetPath().size() < 3 || url->GetPath().size() > 4) return false; diff --git a/lib/remote/templatequeryhandler.hpp b/lib/remote/templatequeryhandler.hpp index 312cf4221..c62670610 100644 --- a/lib/remote/templatequeryhandler.hpp +++ b/lib/remote/templatequeryhandler.hpp @@ -16,11 +16,8 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) override; diff --git a/lib/remote/typequeryhandler.cpp b/lib/remote/typequeryhandler.cpp index b2184344d..ce09293e0 100644 --- a/lib/remote/typequeryhandler.cpp +++ b/lib/remote/typequeryhandler.cpp @@ -49,17 +49,18 @@ public: bool TypeQueryHandler::HandleRequest( const WaitGroup::Ptr&, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) { namespace http = boost::beast::http; + auto url = request.Url(); + auto user = request.User(); + auto params = request.Params(); + if (url->GetPath().size() > 3) return false; diff --git a/lib/remote/typequeryhandler.hpp b/lib/remote/typequeryhandler.hpp index 45cbc38ec..e0567249c 100644 --- a/lib/remote/typequeryhandler.hpp +++ b/lib/remote/typequeryhandler.hpp @@ -16,11 +16,8 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) override; diff --git a/lib/remote/variablequeryhandler.cpp b/lib/remote/variablequeryhandler.cpp index 40552dd7d..b8b62bec1 100644 --- a/lib/remote/variablequeryhandler.cpp +++ b/lib/remote/variablequeryhandler.cpp @@ -59,17 +59,18 @@ public: bool VariableQueryHandler::HandleRequest( const WaitGroup::Ptr&, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) { namespace http = boost::beast::http; + auto url = request.Url(); + auto user = request.User(); + auto params = request.Params(); + if (url->GetPath().size() > 3) return false; diff --git a/lib/remote/variablequeryhandler.hpp b/lib/remote/variablequeryhandler.hpp index d145f5b59..3b7a522ae 100644 --- a/lib/remote/variablequeryhandler.hpp +++ b/lib/remote/variablequeryhandler.hpp @@ -16,11 +16,8 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, AsioTlsStream& stream, - const ApiUser::Ptr& user, - boost::beast::http::request& request, - const Url::Ptr& url, - boost::beast::http::response& response, - const Dictionary::Ptr& params, + const HttpRequest& request, + HttpResponse& response, boost::asio::yield_context& yc, HttpServerConnection& server ) override; From d32f04a86356b8690f391755fa84b8fe72ea9334 Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Wed, 23 Jul 2025 09:44:13 +0200 Subject: [PATCH 411/415] Refactor EventsHandler to stream responses via chunked encoding --- lib/remote/eventshandler.cpp | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/lib/remote/eventshandler.cpp b/lib/remote/eventshandler.cpp index 813d5f41e..ac1fecb74 100644 --- a/lib/remote/eventshandler.cpp +++ b/lib/remote/eventshandler.cpp @@ -102,33 +102,27 @@ bool EventsHandler::HandleRequest( EventsSubscriber subscriber (std::move(eventTypes), HttpUtility::GetLastParameter(params, "filter"), l_ApiQuery); - server.StartDetectClientSideShutdown(); + IoBoundWorkSlot dontLockTheIoThread (yc); response.result(http::status::ok); response.set(http::field::content_type, "application/json"); + response.StartStreaming(true); + // Send response headers before waiting for the first event. + response.Flush(yc); - IoBoundWorkSlot dontLockTheIoThread (yc); - - http::async_write(stream, response, yc); - stream.async_flush(yc); - - asio::const_buffer newLine ("\n", 1); + auto encoder = response.GetJsonEncoder(); for (;;) { auto event (subscriber.GetInbox()->Shift(yc)); - if (event) { - String body = JsonEncode(event); - - boost::algorithm::replace_all(body, "\n", ""); - - asio::const_buffer payload (body.CStr(), body.GetLength()); - - asio::async_write(stream, payload, yc); - asio::async_write(stream, newLine, yc); - stream.async_flush(yc); - } else if (server.Disconnected()) { + if (response.IsClientDisconnected()) { return true; } + + if (event) { + encoder.Encode(event); + response.body() << '\n'; + response.Flush(yc); + } } } From 62b2dadbac828cd7f3da6c8bbe7f7f95a80e716c Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Wed, 23 Jul 2025 09:44:26 +0200 Subject: [PATCH 412/415] Remove extra parameters from HTTP handler signature These parameters are no longer needed since they were only used by EventsHandler which was refactored in an earlier commit. --- lib/remote/actionshandler.cpp | 4 +--- lib/remote/actionshandler.hpp | 4 +--- lib/remote/configfileshandler.cpp | 4 +--- lib/remote/configfileshandler.hpp | 4 +--- lib/remote/configpackageshandler.cpp | 4 +--- lib/remote/configpackageshandler.hpp | 4 +--- lib/remote/configstageshandler.cpp | 4 +--- lib/remote/configstageshandler.hpp | 4 +--- lib/remote/consolehandler.cpp | 4 +--- lib/remote/consolehandler.hpp | 4 +--- lib/remote/createobjecthandler.cpp | 4 +--- lib/remote/createobjecthandler.hpp | 4 +--- lib/remote/deleteobjecthandler.cpp | 4 +--- lib/remote/deleteobjecthandler.hpp | 4 +--- lib/remote/eventshandler.cpp | 4 +--- lib/remote/eventshandler.hpp | 4 +--- lib/remote/httphandler.cpp | 6 ++---- lib/remote/httphandler.hpp | 8 ++------ lib/remote/httpserverconnection.cpp | 25 ++++--------------------- lib/remote/infohandler.cpp | 4 +--- lib/remote/infohandler.hpp | 4 +--- lib/remote/mallocinfohandler.cpp | 4 +--- lib/remote/mallocinfohandler.hpp | 4 +--- lib/remote/modifyobjecthandler.cpp | 4 +--- lib/remote/modifyobjecthandler.hpp | 4 +--- lib/remote/objectqueryhandler.cpp | 4 +--- lib/remote/objectqueryhandler.hpp | 4 +--- lib/remote/statushandler.cpp | 4 +--- lib/remote/statushandler.hpp | 4 +--- lib/remote/templatequeryhandler.cpp | 4 +--- lib/remote/templatequeryhandler.hpp | 4 +--- lib/remote/typequeryhandler.cpp | 4 +--- lib/remote/typequeryhandler.hpp | 4 +--- lib/remote/variablequeryhandler.cpp | 4 +--- lib/remote/variablequeryhandler.hpp | 4 +--- 35 files changed, 40 insertions(+), 127 deletions(-) diff --git a/lib/remote/actionshandler.cpp b/lib/remote/actionshandler.cpp index f0fd713b1..b9853945b 100644 --- a/lib/remote/actionshandler.cpp +++ b/lib/remote/actionshandler.cpp @@ -17,11 +17,9 @@ REGISTER_URLHANDLER("/v1/actions", ActionsHandler); bool ActionsHandler::HandleRequest( const WaitGroup::Ptr& waitGroup, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) { namespace http = boost::beast::http; diff --git a/lib/remote/actionshandler.hpp b/lib/remote/actionshandler.hpp index 83132eeec..3ba856f69 100644 --- a/lib/remote/actionshandler.hpp +++ b/lib/remote/actionshandler.hpp @@ -17,11 +17,9 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) override; }; diff --git a/lib/remote/configfileshandler.cpp b/lib/remote/configfileshandler.cpp index 9a4da43ff..2bd540386 100644 --- a/lib/remote/configfileshandler.cpp +++ b/lib/remote/configfileshandler.cpp @@ -15,11 +15,9 @@ REGISTER_URLHANDLER("/v1/config/files", ConfigFilesHandler); bool ConfigFilesHandler::HandleRequest( const WaitGroup::Ptr&, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) { namespace http = boost::beast::http; diff --git a/lib/remote/configfileshandler.hpp b/lib/remote/configfileshandler.hpp index 0bb12488d..3294811c0 100644 --- a/lib/remote/configfileshandler.hpp +++ b/lib/remote/configfileshandler.hpp @@ -15,11 +15,9 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) override; }; diff --git a/lib/remote/configpackageshandler.cpp b/lib/remote/configpackageshandler.cpp index 0f1009bfd..7e0c7b02c 100644 --- a/lib/remote/configpackageshandler.cpp +++ b/lib/remote/configpackageshandler.cpp @@ -13,11 +13,9 @@ REGISTER_URLHANDLER("/v1/config/packages", ConfigPackagesHandler); bool ConfigPackagesHandler::HandleRequest( const WaitGroup::Ptr&, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) { namespace http = boost::beast::http; diff --git a/lib/remote/configpackageshandler.hpp b/lib/remote/configpackageshandler.hpp index 95bcfacbc..172690f63 100644 --- a/lib/remote/configpackageshandler.hpp +++ b/lib/remote/configpackageshandler.hpp @@ -15,11 +15,9 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) override; private: diff --git a/lib/remote/configstageshandler.cpp b/lib/remote/configstageshandler.cpp index 8ee99fbdd..b08270e56 100644 --- a/lib/remote/configstageshandler.cpp +++ b/lib/remote/configstageshandler.cpp @@ -20,11 +20,9 @@ static std::mutex l_RunningPackageUpdatesMutex; // Protects the above two variab bool ConfigStagesHandler::HandleRequest( const WaitGroup::Ptr&, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) { namespace http = boost::beast::http; diff --git a/lib/remote/configstageshandler.hpp b/lib/remote/configstageshandler.hpp index f49c2efb1..ec333cc50 100644 --- a/lib/remote/configstageshandler.hpp +++ b/lib/remote/configstageshandler.hpp @@ -15,11 +15,9 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) override; private: diff --git a/lib/remote/consolehandler.cpp b/lib/remote/consolehandler.cpp index c063e5781..e17d7e3c1 100644 --- a/lib/remote/consolehandler.cpp +++ b/lib/remote/consolehandler.cpp @@ -55,11 +55,9 @@ static void EnsureFrameCleanupTimer() bool ConsoleHandler::HandleRequest( const WaitGroup::Ptr&, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) { namespace http = boost::beast::http; diff --git a/lib/remote/consolehandler.hpp b/lib/remote/consolehandler.hpp index c2e302ed3..30fb98f2e 100644 --- a/lib/remote/consolehandler.hpp +++ b/lib/remote/consolehandler.hpp @@ -24,11 +24,9 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) override; static std::vector GetAutocompletionSuggestions(const String& word, ScriptFrame& frame); diff --git a/lib/remote/createobjecthandler.cpp b/lib/remote/createobjecthandler.cpp index 447b74c6d..beff9c987 100644 --- a/lib/remote/createobjecthandler.cpp +++ b/lib/remote/createobjecthandler.cpp @@ -17,11 +17,9 @@ REGISTER_URLHANDLER("/v1/objects", CreateObjectHandler); bool CreateObjectHandler::HandleRequest( const WaitGroup::Ptr& waitGroup, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) { namespace http = boost::beast::http; diff --git a/lib/remote/createobjecthandler.hpp b/lib/remote/createobjecthandler.hpp index 317cf023c..972d7b3bd 100644 --- a/lib/remote/createobjecthandler.hpp +++ b/lib/remote/createobjecthandler.hpp @@ -15,11 +15,9 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) override; }; diff --git a/lib/remote/deleteobjecthandler.cpp b/lib/remote/deleteobjecthandler.cpp index d0f49f83c..cd99f7b28 100644 --- a/lib/remote/deleteobjecthandler.cpp +++ b/lib/remote/deleteobjecthandler.cpp @@ -17,11 +17,9 @@ REGISTER_URLHANDLER("/v1/objects", DeleteObjectHandler); bool DeleteObjectHandler::HandleRequest( const WaitGroup::Ptr& waitGroup, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) { namespace http = boost::beast::http; diff --git a/lib/remote/deleteobjecthandler.hpp b/lib/remote/deleteobjecthandler.hpp index 076f76704..f969facda 100644 --- a/lib/remote/deleteobjecthandler.hpp +++ b/lib/remote/deleteobjecthandler.hpp @@ -15,11 +15,9 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) override; }; diff --git a/lib/remote/eventshandler.cpp b/lib/remote/eventshandler.cpp index ac1fecb74..1b7798c04 100644 --- a/lib/remote/eventshandler.cpp +++ b/lib/remote/eventshandler.cpp @@ -41,11 +41,9 @@ const String l_ApiQuery (""); bool EventsHandler::HandleRequest( const WaitGroup::Ptr&, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) { namespace asio = boost::asio; diff --git a/lib/remote/eventshandler.hpp b/lib/remote/eventshandler.hpp index 68a1f9844..91d5ffe3f 100644 --- a/lib/remote/eventshandler.hpp +++ b/lib/remote/eventshandler.hpp @@ -16,11 +16,9 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) override; }; diff --git a/lib/remote/httphandler.cpp b/lib/remote/httphandler.cpp index b6d8d0f4b..db27da31a 100644 --- a/lib/remote/httphandler.cpp +++ b/lib/remote/httphandler.cpp @@ -48,11 +48,9 @@ void HttpHandler::Register(const Url::Ptr& url, const HttpHandler::Ptr& handler) void HttpHandler::ProcessRequest( const WaitGroup::Ptr& waitGroup, - AsioTlsStream& stream, HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) { Dictionary::Ptr node = m_UrlTree; @@ -106,7 +104,7 @@ void HttpHandler::ProcessRequest( */ try { for (const HttpHandler::Ptr& handler : handlers) { - if (handler->HandleRequest(waitGroup, stream, request, response, yc, server)) { + if (handler->HandleRequest(waitGroup, request, response, yc)) { processed = true; break; } diff --git a/lib/remote/httphandler.hpp b/lib/remote/httphandler.hpp index 0d6bd12b8..77f7d4337 100644 --- a/lib/remote/httphandler.hpp +++ b/lib/remote/httphandler.hpp @@ -30,21 +30,17 @@ public: virtual bool HandleRequest( const WaitGroup::Ptr& waitGroup, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) = 0; static void Register(const Url::Ptr& url, const HttpHandler::Ptr& handler); static void ProcessRequest( const WaitGroup::Ptr& waitGroup, - AsioTlsStream& stream, HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ); private: diff --git a/lib/remote/httpserverconnection.cpp b/lib/remote/httpserverconnection.cpp index cd4ca367b..d8befd211 100644 --- a/lib/remote/httpserverconnection.cpp +++ b/lib/remote/httpserverconnection.cpp @@ -408,12 +408,9 @@ bool EnsureValidBody( } static inline -bool ProcessRequest( - AsioTlsStream& stream, +void ProcessRequest( HttpRequest& request, HttpResponse& response, - HttpServerConnection& server, - bool& connectionReusable, const WaitGroup::Ptr& waitGroup, std::chrono::steady_clock::duration& cpuBoundWorkTime, boost::asio::yield_context& yc @@ -425,12 +422,9 @@ bool ProcessRequest( CpuBoundWork handlingRequest (yc); cpuBoundWorkTime = std::chrono::steady_clock::now() - start; - HttpHandler::ProcessRequest(waitGroup, stream, request, response, yc, server); + HttpHandler::ProcessRequest(waitGroup, request, response, yc); + response.body().Finish(); } catch (const std::exception& ex) { - if (!connectionReusable) { - return false; - } - /* Since we don't know the state the stream is in, we can't send an error response and * have to just cause a disconnect here. */ @@ -439,18 +433,9 @@ bool ProcessRequest( } HttpUtility::SendJsonError(response, request.Params(), 500, "Unhandled exception", DiagnosticInformation(ex)); - response.Flush(yc); - return true; } - if (!connectionReusable) { - return false; - } - - response.body().Finish(); response.Flush(yc); - - return true; } void HttpServerConnection::ProcessMessages(boost::asio::yield_context yc) @@ -536,9 +521,7 @@ void HttpServerConnection::ProcessMessages(boost::asio::yield_context yc) m_Seen = std::numeric_limits::max(); - if (!ProcessRequest(*m_Stream, request, response, *this, m_ConnectionReusable, m_WaitGroup, cpuBoundWorkTime, yc)) { - break; - } + ProcessRequest(request, response, m_WaitGroup, cpuBoundWorkTime, yc); if (!request.keep_alive() || !m_ConnectionReusable) { break; diff --git a/lib/remote/infohandler.cpp b/lib/remote/infohandler.cpp index 9363f7ca0..52d7c4b26 100644 --- a/lib/remote/infohandler.cpp +++ b/lib/remote/infohandler.cpp @@ -10,11 +10,9 @@ REGISTER_URLHANDLER("/", InfoHandler); bool InfoHandler::HandleRequest( const WaitGroup::Ptr&, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) { namespace http = boost::beast::http; diff --git a/lib/remote/infohandler.hpp b/lib/remote/infohandler.hpp index f0f6499a3..e62a497ff 100644 --- a/lib/remote/infohandler.hpp +++ b/lib/remote/infohandler.hpp @@ -15,11 +15,9 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) override; }; diff --git a/lib/remote/mallocinfohandler.cpp b/lib/remote/mallocinfohandler.cpp index 465b47b86..4ca37d555 100644 --- a/lib/remote/mallocinfohandler.cpp +++ b/lib/remote/mallocinfohandler.cpp @@ -19,11 +19,9 @@ REGISTER_URLHANDLER("/v1/debug/malloc_info", MallocInfoHandler); bool MallocInfoHandler::HandleRequest( const WaitGroup::Ptr&, - AsioTlsStream&, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context&, - HttpServerConnection& + boost::asio::yield_context& ) { namespace http = boost::beast::http; diff --git a/lib/remote/mallocinfohandler.hpp b/lib/remote/mallocinfohandler.hpp index fc32341fa..10d8b162f 100644 --- a/lib/remote/mallocinfohandler.hpp +++ b/lib/remote/mallocinfohandler.hpp @@ -14,11 +14,9 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) override; }; diff --git a/lib/remote/modifyobjecthandler.cpp b/lib/remote/modifyobjecthandler.cpp index 4b9157af8..9264e3c64 100644 --- a/lib/remote/modifyobjecthandler.cpp +++ b/lib/remote/modifyobjecthandler.cpp @@ -15,11 +15,9 @@ REGISTER_URLHANDLER("/v1/objects", ModifyObjectHandler); bool ModifyObjectHandler::HandleRequest( const WaitGroup::Ptr& waitGroup, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) { namespace http = boost::beast::http; diff --git a/lib/remote/modifyobjecthandler.hpp b/lib/remote/modifyobjecthandler.hpp index 32ddf176c..abc7f9735 100644 --- a/lib/remote/modifyobjecthandler.hpp +++ b/lib/remote/modifyobjecthandler.hpp @@ -15,11 +15,9 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) override; }; diff --git a/lib/remote/objectqueryhandler.cpp b/lib/remote/objectqueryhandler.cpp index edc282453..c910a653d 100644 --- a/lib/remote/objectqueryhandler.cpp +++ b/lib/remote/objectqueryhandler.cpp @@ -90,11 +90,9 @@ Dictionary::Ptr ObjectQueryHandler::SerializeObjectAttrs(const Object::Ptr& obje bool ObjectQueryHandler::HandleRequest( const WaitGroup::Ptr&, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) { namespace http = boost::beast::http; diff --git a/lib/remote/objectqueryhandler.hpp b/lib/remote/objectqueryhandler.hpp index d26a9e1ca..1c7d25afd 100644 --- a/lib/remote/objectqueryhandler.hpp +++ b/lib/remote/objectqueryhandler.hpp @@ -15,11 +15,9 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) override; private: diff --git a/lib/remote/statushandler.cpp b/lib/remote/statushandler.cpp index 9c597dd98..8a16ad81e 100644 --- a/lib/remote/statushandler.cpp +++ b/lib/remote/statushandler.cpp @@ -70,11 +70,9 @@ public: bool StatusHandler::HandleRequest( const WaitGroup::Ptr&, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) { namespace http = boost::beast::http; diff --git a/lib/remote/statushandler.hpp b/lib/remote/statushandler.hpp index 1d05347d1..dceb58ac2 100644 --- a/lib/remote/statushandler.hpp +++ b/lib/remote/statushandler.hpp @@ -15,11 +15,9 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) override; }; diff --git a/lib/remote/templatequeryhandler.cpp b/lib/remote/templatequeryhandler.cpp index 9dceabb7b..81261f02d 100644 --- a/lib/remote/templatequeryhandler.cpp +++ b/lib/remote/templatequeryhandler.cpp @@ -77,11 +77,9 @@ public: bool TemplateQueryHandler::HandleRequest( const WaitGroup::Ptr&, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) { namespace http = boost::beast::http; diff --git a/lib/remote/templatequeryhandler.hpp b/lib/remote/templatequeryhandler.hpp index c62670610..3b3b58cc4 100644 --- a/lib/remote/templatequeryhandler.hpp +++ b/lib/remote/templatequeryhandler.hpp @@ -15,11 +15,9 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) override; }; diff --git a/lib/remote/typequeryhandler.cpp b/lib/remote/typequeryhandler.cpp index ce09293e0..dda19cd12 100644 --- a/lib/remote/typequeryhandler.cpp +++ b/lib/remote/typequeryhandler.cpp @@ -48,11 +48,9 @@ public: bool TypeQueryHandler::HandleRequest( const WaitGroup::Ptr&, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) { namespace http = boost::beast::http; diff --git a/lib/remote/typequeryhandler.hpp b/lib/remote/typequeryhandler.hpp index e0567249c..f065d2471 100644 --- a/lib/remote/typequeryhandler.hpp +++ b/lib/remote/typequeryhandler.hpp @@ -15,11 +15,9 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) override; }; diff --git a/lib/remote/variablequeryhandler.cpp b/lib/remote/variablequeryhandler.cpp index b8b62bec1..e96f6abf8 100644 --- a/lib/remote/variablequeryhandler.cpp +++ b/lib/remote/variablequeryhandler.cpp @@ -58,11 +58,9 @@ public: bool VariableQueryHandler::HandleRequest( const WaitGroup::Ptr&, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) { namespace http = boost::beast::http; diff --git a/lib/remote/variablequeryhandler.hpp b/lib/remote/variablequeryhandler.hpp index 3b7a522ae..b6706037e 100644 --- a/lib/remote/variablequeryhandler.hpp +++ b/lib/remote/variablequeryhandler.hpp @@ -15,11 +15,9 @@ public: bool HandleRequest( const WaitGroup::Ptr& waitGroup, - AsioTlsStream& stream, const HttpRequest& request, HttpResponse& response, - boost::asio::yield_context& yc, - HttpServerConnection& server + boost::asio::yield_context& yc ) override; }; From bb75d7301282e7c034abbef78cdacc87234915a7 Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Fri, 27 Jun 2025 12:46:15 +0200 Subject: [PATCH 413/415] Refactor ObjectQueryHandler to use new JSON stream encoder --- lib/remote/objectqueryhandler.cpp | 118 +++++++++++++++++------------- 1 file changed, 67 insertions(+), 51 deletions(-) diff --git a/lib/remote/objectqueryhandler.cpp b/lib/remote/objectqueryhandler.cpp index c910a653d..4384abb55 100644 --- a/lib/remote/objectqueryhandler.cpp +++ b/lib/remote/objectqueryhandler.cpp @@ -1,6 +1,8 @@ /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ #include "remote/objectqueryhandler.hpp" +#include "base/generator.hpp" +#include "base/json.hpp" #include "remote/httputility.hpp" #include "remote/filterutility.hpp" #include "base/serializer.hpp" @@ -9,6 +11,7 @@ #include #include #include +#include using namespace icinga; @@ -144,6 +147,22 @@ bool ObjectQueryHandler::HandleRequest( return true; } + bool includeUsedBy = false; + bool includeLocation = false; + if (umetas) { + ObjectLock olock(umetas); + for (String meta : umetas) { + if (meta == "used_by") { + includeUsedBy = true; + } else if (meta == "location") { + includeLocation = true; + } else { + HttpUtility::SendJsonError(response, params, 400, "Invalid field specified for meta: " + meta); + return true; + } + } + } + bool allJoins = HttpUtility::GetLastParameter(params, "all_joins"); params->Set("type", type->GetName()); @@ -165,10 +184,7 @@ bool ObjectQueryHandler::HandleRequest( return true; } - ArrayData results; - results.reserve(objs.size()); - - std::set joinAttrs; + std::set joinAttrs; std::set userJoinAttrs; if (ujoins) { @@ -187,70 +203,63 @@ bool ObjectQueryHandler::HandleRequest( if (!allJoins && userJoinAttrs.find(field.NavigationName) == userJoinAttrs.end()) continue; - joinAttrs.insert(field.Name); + joinAttrs.insert(fid); } std::unordered_map>> typePermissions; std::unordered_map objectAccessAllowed; - for (ConfigObject::Ptr obj : objs) { + auto it = objs.begin(); + auto generatorFunc = [&]() -> std::optional { + if (it == objs.end()) { + return std::nullopt; + } + + ConfigObject::Ptr obj = *it; + ++it; + DictionaryData result1{ { "name", obj->GetName() }, { "type", obj->GetReflectionType()->GetName() } }; DictionaryData metaAttrs; + if (includeUsedBy) { + Array::Ptr used_by = new Array(); + metaAttrs.emplace_back("used_by", used_by); - if (umetas) { - ObjectLock olock(umetas); - for (String meta : umetas) { - if (meta == "used_by") { - Array::Ptr used_by = new Array(); - metaAttrs.emplace_back("used_by", used_by); - - for (auto& configObj : DependencyGraph::GetChildren(obj)) { - used_by->Add(new Dictionary({ - { "type", configObj->GetReflectionType()->GetName() }, - { "name", configObj->GetName() } - })); - } - } else if (meta == "location") { - metaAttrs.emplace_back("location", obj->GetSourceLocation()); - } else { - HttpUtility::SendJsonError(response, params, 400, "Invalid field specified for meta: " + meta); - return true; - } + for (auto& configObj : DependencyGraph::GetChildren(obj)) { + used_by->Add(new Dictionary({ + {"type", configObj->GetReflectionType()->GetName()}, + {"name", configObj->GetName()} + })); } } + if (includeLocation) { + metaAttrs.emplace_back("location", obj->GetSourceLocation()); + } + result1.emplace_back("meta", new Dictionary(std::move(metaAttrs))); try { result1.emplace_back("attrs", SerializeObjectAttrs(obj, String(), uattrs, false, false)); } catch (const ScriptError& ex) { - HttpUtility::SendJsonError(response, params, 400, ex.what()); - return true; + return new Dictionary{ + {"type", type->GetName()}, + {"name", obj->GetName()}, + {"code", 400}, + {"status", ex.what()} + }; } DictionaryData joins; - for (const String& joinAttr : joinAttrs) { + for (auto joinAttr : joinAttrs) { Object::Ptr joinedObj; - int fid = type->GetFieldId(joinAttr); + Field field = type->GetFieldInfo(joinAttr); - if (fid < 0) { - HttpUtility::SendJsonError(response, params, 400, "Invalid field specified for join: " + joinAttr); - return true; - } - - Field field = type->GetFieldInfo(fid); - - if (!(field.Attributes & FANavigation)) { - HttpUtility::SendJsonError(response, params, 400, "Not a joinable field: " + joinAttr); - return true; - } - - joinedObj = obj->NavigateField(fid); + joinedObj = obj->NavigateField(joinAttr); if (!joinedObj) continue; @@ -303,22 +312,29 @@ bool ObjectQueryHandler::HandleRequest( try { joins.emplace_back(prefix, SerializeObjectAttrs(joinedObj, prefix, ujoins, true, allJoins)); } catch (const ScriptError& ex) { - HttpUtility::SendJsonError(response, params, 400, ex.what()); - return true; + return new Dictionary{ + {"type", type->GetName()}, + {"name", obj->GetName()}, + {"code", 400}, + {"status", ex.what()} + }; } } result1.emplace_back("joins", new Dictionary(std::move(joins))); - results.push_back(new Dictionary(std::move(result1))); - } - - Dictionary::Ptr result = new Dictionary({ - { "results", new Array(std::move(results)) } - }); + return new Dictionary{std::move(result1)}; + }; response.result(http::status::ok); - HttpUtility::SendJsonBody(response, params, result); + response.set(http::field::content_type, "application/json"); + response.StartStreaming(); + + Dictionary::Ptr results = new Dictionary{{"results", new ValueGenerator{generatorFunc}}}; + results->Freeze(); + + bool pretty = HttpUtility::GetLastParameter(params, "pretty"); + response.GetJsonEncoder(pretty).Encode(results, &yc); return true; } From 4782ea8a75482a0c2d1521e96a4f3149e2bb79fc Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Tue, 15 Jul 2025 11:30:11 +0200 Subject: [PATCH 414/415] Make inherited protected functions of ApiListener public This is needed so it's possible to manually add an ApiListener object for the purpose of unit-testing. --- lib/remote/apilistener.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/remote/apilistener.hpp b/lib/remote/apilistener.hpp index f278c2e9b..0b0e29f33 100644 --- a/lib/remote/apilistener.hpp +++ b/lib/remote/apilistener.hpp @@ -161,12 +161,12 @@ public: return m_WaitGroup; } -protected: void OnConfigLoaded() override; void OnAllConfigLoaded() override; void Start(bool runtimeCreated) override; void Stop(bool runtimeDeleted) override; +protected: void ValidateTlsProtocolmin(const Lazy& lvalue, const ValidationUtils& utils) override; void ValidateTlsHandshakeTimeout(const Lazy& lvalue, const ValidationUtils& utils) override; From 7373f36cc5651017acff9bb0f13c8cb4b494b885 Mon Sep 17 00:00:00 2001 From: Johannes Schmidt Date: Tue, 22 Jul 2025 13:59:58 +0200 Subject: [PATCH 415/415] Add unit-tests for HttpServerConnection and HTTP message classes --- test/CMakeLists.txt | 70 ++++ test/base-configuration-fixture.hpp | 56 +++ test/base-testloggerfixture.hpp | 127 ++++++ test/base-tlsstream-fixture.hpp | 114 ++++++ test/remote-certificate-fixture.cpp | 42 ++ test/remote-certificate-fixture.hpp | 69 ++++ test/remote-httpmessage.cpp | 351 +++++++++++++++++ test/remote-httpserverconnection.cpp | 558 +++++++++++++++++++++++++++ 8 files changed, 1387 insertions(+) create mode 100644 test/base-configuration-fixture.hpp create mode 100644 test/base-testloggerfixture.hpp create mode 100644 test/base-tlsstream-fixture.hpp create mode 100644 test/remote-certificate-fixture.cpp create mode 100644 test/remote-certificate-fixture.hpp create mode 100644 test/remote-httpmessage.cpp create mode 100644 test/remote-httpserverconnection.cpp diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index c87679b08..5498a6d83 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -87,7 +87,10 @@ set(base_test_SOURCES icinga-notification.cpp icinga-perfdata.cpp methods-pluginnotificationtask.cpp + remote-certificate-fixture.cpp remote-configpackageutility.cpp + remote-httpserverconnection.cpp + remote-httpmessage.cpp remote-url.cpp ${base_OBJS} $ @@ -271,6 +274,33 @@ add_boost_test(base icinga_perfdata/parse_edgecases icinga_perfdata/empty_warn_crit_min_max methods_pluginnotificationtask/truncate_long_output + remote_certs_fixture/prepare_directory + remote_certs_fixture/cleanup_certs + remote_httpmessage/request_parse + remote_httpmessage/request_params + remote_httpmessage/response_clear + remote_httpmessage/response_flush_nothrow + remote_httpmessage/response_flush_throw + remote_httpmessage/response_write_empty + remote_httpmessage/response_write_fixed + remote_httpmessage/response_write_chunked + remote_httpmessage/response_sendjsonbody + remote_httpmessage/response_sendjsonerror + remote_httpmessage/response_sendfile + remote_httpserverconnection/expect_100_continue + remote_httpserverconnection/bad_request + remote_httpserverconnection/error_access_control + remote_httpserverconnection/error_accept_header + remote_httpserverconnection/authenticate_cn + remote_httpserverconnection/authenticate_passwd + remote_httpserverconnection/authenticate_error_wronguser + remote_httpserverconnection/authenticate_error_wrongpasswd + remote_httpserverconnection/reuse_connection + remote_httpserverconnection/wg_abort + remote_httpserverconnection/client_shutdown + remote_httpserverconnection/handler_throw_error + remote_httpserverconnection/handler_throw_streaming + remote_httpserverconnection/liveness_disconnect remote_configpackageutility/ValidateName remote_url/id_and_path remote_url/parameters @@ -279,6 +309,46 @@ add_boost_test(base remote_url/illegal_legal_strings ) +if(BUILD_TESTING) + set_tests_properties( + base-remote_httpmessage/request_parse + base-remote_httpmessage/request_params + base-remote_httpmessage/response_clear + base-remote_httpmessage/response_flush_nothrow + base-remote_httpmessage/response_flush_throw + base-remote_httpmessage/response_write_empty + base-remote_httpmessage/response_write_fixed + base-remote_httpmessage/response_write_chunked + base-remote_httpmessage/response_sendjsonbody + base-remote_httpmessage/response_sendjsonerror + base-remote_httpmessage/response_sendfile + base-remote_httpserverconnection/expect_100_continue + base-remote_httpserverconnection/bad_request + base-remote_httpserverconnection/error_access_control + base-remote_httpserverconnection/error_accept_header + base-remote_httpserverconnection/authenticate_cn + base-remote_httpserverconnection/authenticate_passwd + base-remote_httpserverconnection/authenticate_error_wronguser + base-remote_httpserverconnection/authenticate_error_wrongpasswd + base-remote_httpserverconnection/reuse_connection + base-remote_httpserverconnection/wg_abort + base-remote_httpserverconnection/client_shutdown + base-remote_httpserverconnection/handler_throw_error + base-remote_httpserverconnection/handler_throw_streaming + base-remote_httpserverconnection/liveness_disconnect + PROPERTIES FIXTURES_REQUIRED ssl_certs) + + set_tests_properties( + base-remote_certs_fixture/prepare_directory + PROPERTIES FIXTURES_SETUP ssl_certs + ) + + set_tests_properties( + base-remote_certs_fixture/cleanup_certs + PROPERTIES FIXTURES_CLEANUP ssl_certs + ) +endif() + if(ICINGA2_WITH_LIVESTATUS) set(livestatus_test_SOURCES icingaapplication-fixture.cpp diff --git a/test/base-configuration-fixture.hpp b/test/base-configuration-fixture.hpp new file mode 100644 index 000000000..2639eb3ff --- /dev/null +++ b/test/base-configuration-fixture.hpp @@ -0,0 +1,56 @@ +/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */ + +#ifndef CONFIGURATION_FIXTURE_H +#define CONFIGURATION_FIXTURE_H + +#include "base/configuration.hpp" +#include +#include + +namespace icinga { + +struct ConfigurationDataDirFixture +{ + ConfigurationDataDirFixture() + : m_DataDir(boost::filesystem::current_path() / "data"), m_PrevDataDir(Configuration::DataDir.GetData()) + { + boost::filesystem::create_directories(m_DataDir); + Configuration::DataDir = m_DataDir.string(); + } + + ~ConfigurationDataDirFixture() + { + boost::filesystem::remove_all(m_DataDir); + Configuration::DataDir = m_PrevDataDir.string(); + } + + boost::filesystem::path m_DataDir; + +private: + boost::filesystem::path m_PrevDataDir; +}; + +struct ConfigurationCacheDirFixture +{ + ConfigurationCacheDirFixture() + : m_CacheDir(boost::filesystem::current_path() / "cache"), m_PrevCacheDir(Configuration::CacheDir.GetData()) + { + boost::filesystem::create_directories(m_CacheDir); + Configuration::CacheDir = m_CacheDir.string(); + } + + ~ConfigurationCacheDirFixture() + { + boost::filesystem::remove_all(m_CacheDir); + Configuration::CacheDir = m_PrevCacheDir.string(); + } + + boost::filesystem::path m_CacheDir; + +private: + boost::filesystem::path m_PrevCacheDir; +}; + +} // namespace icinga + +#endif // CONFIGURATION_FIXTURE_H diff --git a/test/base-testloggerfixture.hpp b/test/base-testloggerfixture.hpp new file mode 100644 index 000000000..69c073b02 --- /dev/null +++ b/test/base-testloggerfixture.hpp @@ -0,0 +1,127 @@ +/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */ + +#ifndef TEST_LOGGER_FIXTURE_H +#define TEST_LOGGER_FIXTURE_H + +#include +#include "base/logger.hpp" +#include +#include +#include +#include + +namespace icinga { + +class TestLogger : public Logger +{ +public: + DECLARE_PTR_TYPEDEFS(TestLogger); + + struct Expect + { + std::string pattern; + std::promise prom; + }; + + auto ExpectLogPattern(const std::string& pattern, + const std::chrono::milliseconds& timeout = std::chrono::seconds(0)) + { + std::unique_lock lock(m_Mutex); + for (const auto& logEntry : m_LogEntries) { + if (boost::regex_match(logEntry.Message.GetData(), boost::regex(pattern))) { + return boost::test_tools::assertion_result{true}; + } + } + + if (timeout == std::chrono::seconds(0)) { + return boost::test_tools::assertion_result{false}; + } + + auto expect = std::make_shared(Expect{pattern, std::promise()}); + m_Expects.emplace_back(expect); + lock.unlock(); + + auto future = expect->prom.get_future(); + auto status = future.wait_for(timeout); + boost::test_tools::assertion_result ret{status == std::future_status::ready && future.get()}; + ret.message() << "Pattern \"" << pattern << "\" in log within " << timeout.count() << "ms"; + + lock.lock(); + m_Expects.erase(boost::range::remove(m_Expects, expect), m_Expects.end()); + + return ret; + } + +private: + void ProcessLogEntry(const LogEntry& entry) override + { + std::unique_lock lock(m_Mutex); + m_LogEntries.push_back(entry); + + auto it = boost::range::remove_if(m_Expects, [&entry](const std::shared_ptr& expect) { + if (boost::regex_match(entry.Message.GetData(), boost::regex(expect->pattern))) { + expect->prom.set_value(true); + return true; + } + return false; + }); + m_Expects.erase(it, m_Expects.end()); + } + + void Flush() override {} + + std::mutex m_Mutex; + std::vector> m_Expects; + std::vector m_LogEntries; +}; + +/** + * A fixture to capture log entries and assert their presence in tests. + * + * Currently, this only supports checking existing entries and waiting for new ones + * using ExpectLogPattern(), but more functionality can easily be added in the future, + * like only asserting on past log messages, only waiting for new ones, asserting log + * entry metadata (severity etc.) and so on. + */ +struct TestLoggerFixture +{ + TestLoggerFixture() + { + testLogger->SetSeverity(testLogger->SeverityToString(LogDebug)); + testLogger->Activate(true); + testLogger->SetActive(true); + } + + ~TestLoggerFixture() + { + testLogger->SetActive(false); + testLogger->Deactivate(true); + } + + /** + * Asserts the presence of a log entry that matches the given regex pattern. + * + * First, the existing log entries are searched for the pattern. If the pattern isn't found, + * until the timeout is reached, the function will wait if a new log message is added that + * matches the pattern. + * + * A boost assertion result object is returned, that evaluates to bool, but contains an + * error message that is printed by the testing framework when the assert failed. + * + * @param pattern The regex pattern the log message needs to match + * @param timeout The maximum amount of time to wait for the log message to arrive + * + * @return A @c boost::test_tools::assertion_result object that can be used in BOOST_REQUIRE + */ + auto ExpectLogPattern(const std::string& pattern, + const std::chrono::milliseconds& timeout = std::chrono::seconds(0)) + { + return testLogger->ExpectLogPattern(pattern, timeout); + } + + TestLogger::Ptr testLogger = new TestLogger; +}; + +} // namespace icinga + +#endif // TEST_LOGGER_FIXTURE_H diff --git a/test/base-tlsstream-fixture.hpp b/test/base-tlsstream-fixture.hpp new file mode 100644 index 000000000..3d1327b63 --- /dev/null +++ b/test/base-tlsstream-fixture.hpp @@ -0,0 +1,114 @@ +/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */ + +#pragma once + +#include "base/io-engine.hpp" +#include "base/tlsstream.hpp" +#include "test/remote-certificate-fixture.hpp" +#include +#include + +namespace icinga { + +/** + * Creates a pair of TLS Streams on a random unused port. + */ +struct TlsStreamFixture : CertificateFixture +{ + TlsStreamFixture() + { + using namespace boost::asio::ip; + using handshake_type = boost::asio::ssl::stream_base::handshake_type; + + auto serverCert = EnsureCertFor("server"); + auto clientCert = EnsureCertFor("client"); + + auto& io = IoEngine::Get().GetIoContext(); + + m_ClientSslContext = SetupSslContext(clientCert.crtFile, clientCert.keyFile, m_CaCrtFile.string(), "", + DEFAULT_TLS_CIPHERS, DEFAULT_TLS_PROTOCOLMIN, DebugInfo()); + client = Shared::Make(io, *m_ClientSslContext); + + m_ServerSslContext = SetupSslContext(serverCert.crtFile, serverCert.keyFile, m_CaCrtFile.string(), "", + DEFAULT_TLS_CIPHERS, DEFAULT_TLS_PROTOCOLMIN, DebugInfo()); + server = Shared::Make(io, *m_ServerSslContext); + + std::promise p; + + tcp::acceptor acceptor{io, tcp::endpoint{address_v4::loopback(), 0}}; + acceptor.listen(); + acceptor.async_accept(server->lowest_layer(), [&](const boost::system::error_code& ec) { + if (ec) { + BOOST_TEST_MESSAGE("Server Accept Error: " + ec.message()); + p.set_exception(std::make_exception_ptr(boost::system::system_error{ec})); + return; + } + server->next_layer().async_handshake(handshake_type::server, [&](const boost::system::error_code& ec) { + if (ec) { + BOOST_TEST_MESSAGE("Server Handshake Error: " + ec.message()); + p.set_exception(std::make_exception_ptr(boost::system::system_error{ec})); + return; + } + + if (!server->next_layer().IsVerifyOK()) { + p.set_exception(std::make_exception_ptr(std::runtime_error{"Verify failed on server-side."})); + } + + p.set_value(); + }); + }); + + auto f = p.get_future(); + boost::system::error_code ec; + if (client->lowest_layer().connect(acceptor.local_endpoint(), ec)) { + BOOST_TEST_MESSAGE("Client Connect error: " + ec.message()); + f.get(); + BOOST_THROW_EXCEPTION(boost::system::system_error{ec}); + } + + if (client->next_layer().handshake(handshake_type::client, ec)) { + BOOST_TEST_MESSAGE("Client Handshake error: " + ec.message()); + f.get(); + BOOST_THROW_EXCEPTION(boost::system::system_error{ec}); + } + + if (!client->next_layer().IsVerifyOK()) { + f.get(); + BOOST_THROW_EXCEPTION(std::runtime_error{"Verify failed on client-side."}); + } + + f.get(); + } + + auto Shutdown(const Shared::Ptr& stream, std::optional yc = {}) + { + boost::system::error_code ec; + if (yc) { + stream->next_layer().async_shutdown((*yc)[ec]); + } else { + stream->next_layer().shutdown(ec); + } +#if BOOST_VERSION < 107000 + /* On boost versions < 1.70, the end-of-file condition was propagated as an error, + * even in case of a successful shutdown. This is information can be found in the + * changelog for the boost Asio 1.14.0 / Boost 1.70 release. + */ + if (ec == boost::asio::error::eof) { + BOOST_TEST_MESSAGE("Shutdown completed successfully with 'boost::asio::error::eof'."); + return boost::test_tools::assertion_result{true}; + } +#endif + boost::test_tools::assertion_result ret{!ec}; + ret.message() << "Error: " << ec.message(); + return ret; + } + + Shared::Ptr client; + Shared::Ptr server; + +private: + Shared::Ptr m_ClientSslContext; + Shared::Ptr m_ServerSslContext; +}; + +} // namespace icinga diff --git a/test/remote-certificate-fixture.cpp b/test/remote-certificate-fixture.cpp new file mode 100644 index 000000000..adb260740 --- /dev/null +++ b/test/remote-certificate-fixture.cpp @@ -0,0 +1,42 @@ +/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */ + +#include "remote-certificate-fixture.hpp" +#include + +using namespace icinga; + +const boost::filesystem::path CertificateFixture::m_PersistentCertsDir = + boost::filesystem::current_path() / "persistent" / "certs"; + +BOOST_AUTO_TEST_SUITE(remote_certs_fixture) + +/** + * Recursively removes the directory that contains the test certificates. + * + * This needs to be done once initially to prepare the directory, in case there are any + * left-overs from previous test runs, and once after all tests using the certificates + * have been completed. + * + * This dependency is expressed as a CTest fixture and not a boost-test one, because that + * is the only way to have persistency between individual test-cases with CTest. + */ +static void CleanupPersistentCertificateDir() +{ + if (boost::filesystem::exists(CertificateFixture::m_PersistentCertsDir)) { + boost::filesystem::remove_all(CertificateFixture::m_PersistentCertsDir); + } +} + +BOOST_FIXTURE_TEST_CASE(prepare_directory, ConfigurationDataDirFixture) +{ + // Remove any existing left-overs of the persistent certificate directory from a previous + // test run. + CleanupPersistentCertificateDir(); +} + +BOOST_FIXTURE_TEST_CASE(cleanup_certs, ConfigurationDataDirFixture) +{ + CleanupPersistentCertificateDir(); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/remote-certificate-fixture.hpp b/test/remote-certificate-fixture.hpp new file mode 100644 index 000000000..1e8ad645a --- /dev/null +++ b/test/remote-certificate-fixture.hpp @@ -0,0 +1,69 @@ +/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */ + +#pragma once + +#include "remote/apilistener.hpp" +#include "remote/pkiutility.hpp" +#include "test/base-configuration-fixture.hpp" +#include + +namespace icinga { + +struct CertificateFixture : ConfigurationDataDirFixture +{ + CertificateFixture() + { + namespace fs = boost::filesystem; + + m_CaDir = ApiListener::GetCaDir(); + m_CertsDir = ApiListener::GetCertsDir(); + m_CaCrtFile = m_CertsDir / "ca.crt"; + + fs::create_directories(m_PersistentCertsDir / "ca"); + fs::create_directories(m_PersistentCertsDir / "certs"); + + if (fs::exists(m_DataDir / "ca")) { + fs::remove(m_DataDir / "ca"); + } + if (fs::exists(m_DataDir / "certs")) { + fs::remove(m_DataDir / "certs"); + } + + fs::create_directory_symlink(m_PersistentCertsDir / "certs", m_DataDir / "certs"); + fs::create_directory_symlink(m_PersistentCertsDir / "ca", m_DataDir / "ca"); + + if (!fs::exists(m_CaCrtFile)) { + PkiUtility::NewCa(); + fs::copy_file(m_CaDir / "ca.crt", m_CaCrtFile); + } + } + + auto EnsureCertFor(const std::string& name) + { + struct Cert + { + String crtFile; + String keyFile; + String csrFile; + }; + + Cert cert; + cert.crtFile = (m_CertsDir / (name + ".crt")).string(); + cert.keyFile = (m_CertsDir / (name + ".key")).string(); + cert.csrFile = (m_CertsDir / (name + ".csr")).string(); + + if (!Utility::PathExists(cert.crtFile)) { + PkiUtility::NewCert(name, cert.keyFile, cert.csrFile, cert.crtFile); + PkiUtility::SignCsr(cert.csrFile, cert.crtFile); + } + + return cert; + } + + boost::filesystem::path m_CaDir; + boost::filesystem::path m_CertsDir; + boost::filesystem::path m_CaCrtFile; + static const boost::filesystem::path m_PersistentCertsDir; +}; + +} // namespace icinga diff --git a/test/remote-httpmessage.cpp b/test/remote-httpmessage.cpp new file mode 100644 index 000000000..5c79d1cc2 --- /dev/null +++ b/test/remote-httpmessage.cpp @@ -0,0 +1,351 @@ +/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */ + +#include +#include "base/base64.hpp" +#include "base/json.hpp" +#include "remote/httpmessage.hpp" +#include "remote/httputility.hpp" +#include "test/base-tlsstream-fixture.hpp" +#include +#include + +using namespace icinga; +using namespace boost::beast; + +static std::future SpawnSynchronizedCoroutine(std::function fn) +{ + auto promise = std::make_unique>(); + auto future = promise->get_future(); + auto& io = IoEngine::Get().GetIoContext(); + IoEngine::SpawnCoroutine(io, [promise = std::move(promise), fn = std::move(fn)](boost::asio::yield_context yc) { + try { + fn(std::move(yc)); + } catch (const std::exception&) { + promise->set_exception(std::current_exception()); + return; + } + promise->set_value(); + }); + return future; +} + +BOOST_FIXTURE_TEST_SUITE(remote_httpmessage, TlsStreamFixture) + +BOOST_AUTO_TEST_CASE(request_parse) +{ + http::request requestOut; + requestOut.method(http::verb::get); + requestOut.target("https://localhost:5665/v1/test"); + requestOut.set(http::field::authorization, "Basic " + Base64::Encode("invalid:invalid")); + requestOut.set(http::field::accept, "application/json"); + requestOut.set(http::field::connection, "close"); + requestOut.body() = "test"; + requestOut.prepare_payload(); + + auto future = SpawnSynchronizedCoroutine([this, &requestOut](boost::asio::yield_context yc) { + boost::beast::flat_buffer buf; + HttpRequest request(server); + BOOST_REQUIRE_NO_THROW(request.ParseHeader(buf, yc)); + + for (const auto& field : requestOut.base()) { + BOOST_REQUIRE(request.count(field.name())); + } + + BOOST_REQUIRE_NO_THROW(request.ParseBody(buf, yc)); + BOOST_REQUIRE_EQUAL(request.body(), "test"); + + Shutdown(server, yc); + }); + + http::write(*client, requestOut); + client->flush(); + + Shutdown(client); + future.get(); +} + +BOOST_AUTO_TEST_CASE(request_params) +{ + HttpRequest request(client); + // clang-format off + request.body() = JsonEncode( + new Dictionary{ + {"bool-in-json", true}, + {"bool-in-url-and-json", true}, + {"string-in-json", "json-value"}, + {"string-in-url-and-json", "json-value"} + }); + request.target("https://localhost:1234/v1/test?" + "bool-in-url-and-json=0&" + "bool-in-url=1&" + "string-in-url-and-json=url-value&" + "string-only-in-url=url-value" + ); + // clang-format on + + // Test pointer being valid after decode + request.DecodeParams(); + auto params = request.Params(); + BOOST_REQUIRE(params); + + // Test JSON-only params being parsed as their correct type + BOOST_REQUIRE(params->Get("bool-in-json").IsBoolean()); + BOOST_REQUIRE(params->Get("string-in-json").IsString()); + BOOST_REQUIRE(params->Get("bool-in-url-and-json").IsObjectType()); + BOOST_REQUIRE(params->Get("string-in-url-and-json").IsObjectType()); + + // Test 0/1 string values from URL evaluate to true and false + // These currently get implicitly converted to double and then to bool, but this is an + // implementation we don't need to test for here. + BOOST_REQUIRE_EQUAL(HttpUtility::GetLastParameter(params, "bool-in-url-and-json"), "0"); + BOOST_REQUIRE(!HttpUtility::GetLastParameter(params, "bool-in-url-and-json")); + BOOST_REQUIRE_EQUAL(HttpUtility::GetLastParameter(params, "bool-in-url"), "1"); + BOOST_REQUIRE(HttpUtility::GetLastParameter(params, "bool-in-url")); + + // Test non-existing parameters evaluate to false + BOOST_REQUIRE(HttpUtility::GetLastParameter(params, "does-not-exist").IsEmpty()); + BOOST_REQUIRE(!HttpUtility::GetLastParameter(params, "does-not-exist")); + + // Test precedence of URL params over JSON params + BOOST_REQUIRE_EQUAL(HttpUtility::GetLastParameter(params, "string-in-json"), "json-value"); + BOOST_REQUIRE_EQUAL(HttpUtility::GetLastParameter(params, "string-in-url-and-json"), "url-value"); + BOOST_REQUIRE_EQUAL(HttpUtility::GetLastParameter(params, "string-only-in-url"), "url-value"); +} + +BOOST_AUTO_TEST_CASE(response_clear) +{ + HttpResponse response(server); + response.result(http::status::bad_request); + response.version(10); + response.set(http::field::content_type, "text/html"); + response.body() << "test"; + + response.Clear(); + + BOOST_REQUIRE(response[http::field::content_type].empty()); + BOOST_REQUIRE_EQUAL(response.result(), http::status::ok); + BOOST_REQUIRE_EQUAL(response.version(), 11); + BOOST_REQUIRE_EQUAL(response.body().Size(), 0); +} + +BOOST_AUTO_TEST_CASE(response_flush_nothrow) +{ + auto future = SpawnSynchronizedCoroutine([this](const boost::asio::yield_context& yc) { + HttpResponse response(server); + response.result(http::status::ok); + + server->lowest_layer().close(); + + boost::beast::error_code ec; + BOOST_REQUIRE_NO_THROW(response.Flush(yc[ec])); + BOOST_REQUIRE_EQUAL(ec, boost::system::errc::bad_file_descriptor); + }); + + auto status = future.wait_for(std::chrono::seconds(1)); + BOOST_REQUIRE(status == std::future_status::ready); +} + +BOOST_AUTO_TEST_CASE(response_flush_throw) +{ + auto future = SpawnSynchronizedCoroutine([this](const boost::asio::yield_context& yc) { + HttpResponse response(server); + response.result(http::status::ok); + + server->lowest_layer().close(); + + BOOST_REQUIRE_EXCEPTION(response.Flush(yc), std::exception, [](const std::exception& ex) { + auto se = dynamic_cast(&ex); + return se && se->code() == boost::system::errc::bad_file_descriptor; + }); + }); + + auto status = future.wait_for(std::chrono::seconds(1)); + BOOST_REQUIRE(status == std::future_status::ready); +} + +BOOST_AUTO_TEST_CASE(response_write_empty) +{ + auto future = SpawnSynchronizedCoroutine([this](boost::asio::yield_context yc) { + HttpResponse response(server); + response.result(http::status::ok); + + BOOST_REQUIRE_NO_THROW(response.Flush(yc)); + + Shutdown(server, yc); + }); + + http::response_parser parser; + flat_buffer buf; + boost::system::error_code ec; + http::read(*client, buf, parser, ec); + + Shutdown(client); + + future.get(); + + BOOST_REQUIRE(!ec); + BOOST_REQUIRE_EQUAL(parser.get().result(), http::status::ok); + BOOST_REQUIRE_EQUAL(parser.get().chunked(), false); + BOOST_REQUIRE_EQUAL(parser.get().body(), ""); +} + +BOOST_AUTO_TEST_CASE(response_write_fixed) +{ + auto future = SpawnSynchronizedCoroutine([this](boost::asio::yield_context yc) { + HttpResponse response(server); + response.result(http::status::ok); + response.body() << "test"; + + BOOST_REQUIRE_NO_THROW(response.Flush(yc)); + + Shutdown(server, yc); + }); + + http::response_parser parser; + flat_buffer buf; + boost::system::error_code ec; + http::read(*client, buf, parser, ec); + + Shutdown(client); + + future.get(); + + BOOST_REQUIRE(!ec); + BOOST_REQUIRE_EQUAL(parser.get().result(), http::status::ok); + BOOST_REQUIRE_EQUAL(parser.get().chunked(), false); + BOOST_REQUIRE_EQUAL(parser.get().body(), "test"); +} + +BOOST_AUTO_TEST_CASE(response_write_chunked) +{ + // NOLINTNEXTLINE(readability-function-cognitive-complexity) + auto future = SpawnSynchronizedCoroutine([this](boost::asio::yield_context yc) { + HttpResponse response(server); + response.result(http::status::ok); + + response.StartStreaming(); + BOOST_REQUIRE_NO_THROW(response.Flush(yc)); + BOOST_REQUIRE(response.HasSerializationStarted()); + + response.body() << "test" << 1; + BOOST_REQUIRE_NO_THROW(response.Flush(yc)); + + response.body() << "test" << 2; + BOOST_REQUIRE_NO_THROW(response.Flush(yc)); + + response.body() << "test" << 3; + response.body().Finish(); + BOOST_REQUIRE_NO_THROW(response.Flush(yc)); + + Shutdown(server, yc); + }); + + http::response_parser parser; + flat_buffer buf; + boost::system::error_code ec; + http::read(*client, buf, parser, ec); + + Shutdown(client); + + future.get(); + + BOOST_REQUIRE(!ec); + BOOST_REQUIRE_EQUAL(parser.get().result(), http::status::ok); + BOOST_REQUIRE_EQUAL(parser.get().chunked(), true); + BOOST_REQUIRE_EQUAL(parser.get().body(), "test1test2test3"); +} + +BOOST_AUTO_TEST_CASE(response_sendjsonbody) +{ + auto future = SpawnSynchronizedCoroutine([this](boost::asio::yield_context yc) { + HttpResponse response(server); + response.result(http::status::ok); + + HttpUtility::SendJsonBody(response, nullptr, new Dictionary{{"test", 1}}); + + BOOST_REQUIRE_NO_THROW(response.Flush(yc)); + + Shutdown(server, yc); + }); + + http::response_parser parser; + flat_buffer buf; + boost::system::error_code ec; + http::read(*client, buf, parser, ec); + + Shutdown(client); + + future.get(); + + BOOST_REQUIRE(!ec); + BOOST_REQUIRE_EQUAL(parser.get().result(), http::status::ok); + BOOST_REQUIRE_EQUAL(parser.get().chunked(), false); + Dictionary::Ptr body = JsonDecode(parser.get().body()); + BOOST_REQUIRE_EQUAL(body->Get("test"), 1); +} + +BOOST_AUTO_TEST_CASE(response_sendjsonerror) +{ + auto future = SpawnSynchronizedCoroutine([this](boost::asio::yield_context yc) { + HttpResponse response(server); + + // This has to be overwritten in SendJsonError. + response.result(http::status::ok); + + HttpUtility::SendJsonError(response, nullptr, 404, "Not found."); + + BOOST_REQUIRE_NO_THROW(response.Flush(yc)); + + Shutdown(server, yc); + }); + + http::response_parser parser; + flat_buffer buf; + boost::system::error_code ec; + http::read(*client, buf, parser, ec); + + Shutdown(client); + + future.get(); + + BOOST_REQUIRE(!ec); + BOOST_REQUIRE_EQUAL(parser.get().result(), http::status::not_found); + BOOST_REQUIRE_EQUAL(parser.get().chunked(), false); + Dictionary::Ptr body = JsonDecode(parser.get().body()); + BOOST_REQUIRE_EQUAL(body->Get("error"), 404); + BOOST_REQUIRE_EQUAL(body->Get("status"), "Not found."); +} + +BOOST_AUTO_TEST_CASE(response_sendfile) +{ + auto future = SpawnSynchronizedCoroutine([this](boost::asio::yield_context yc) { + HttpResponse response(server); + + response.result(http::status::ok); + BOOST_REQUIRE_NO_THROW(response.SendFile(m_CaCrtFile.string(), yc)); + BOOST_REQUIRE_NO_THROW(response.Flush(yc)); + + Shutdown(server, yc); + }); + + http::response_parser parser; + flat_buffer buf; + boost::system::error_code ec; + http::read(*client, buf, parser, ec); + + Shutdown(client); + + future.get(); + + BOOST_REQUIRE(!ec); + BOOST_REQUIRE_EQUAL(parser.get().result(), http::status::ok); + BOOST_REQUIRE_EQUAL(parser.get().chunked(), false); + + std::ifstream fp(m_CaCrtFile.string(), std::ifstream::in | std::ifstream::binary); + fp.exceptions(std::ifstream::badbit); + std::stringstream ss; + ss << fp.rdbuf(); + BOOST_REQUIRE_EQUAL(ss.str(), parser.get().body()); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/test/remote-httpserverconnection.cpp b/test/remote-httpserverconnection.cpp new file mode 100644 index 000000000..e5c202770 --- /dev/null +++ b/test/remote-httpserverconnection.cpp @@ -0,0 +1,558 @@ +/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */ + +#include +#include "base/base64.hpp" +#include "base/json.hpp" +#include "remote/httphandler.hpp" +#include "test/base-testloggerfixture.hpp" +#include "test/base-tlsstream-fixture.hpp" +#include +#include +#include + +using namespace icinga; +using namespace boost::beast; +using namespace boost::unit_test_framework; + +struct HttpServerConnectionFixture : TlsStreamFixture, ConfigurationCacheDirFixture, TestLoggerFixture +{ + HttpServerConnection::Ptr m_Connection; + StoppableWaitGroup::Ptr m_WaitGroup; + + HttpServerConnectionFixture() : m_WaitGroup(new StoppableWaitGroup) {} + + static void CreateApiListener(const String& allowOrigin) + { + ScriptGlobal::Set("NodeName", "server"); + ApiListener::Ptr listener = new ApiListener; + listener->OnConfigLoaded(); + listener->SetAccessControlAllowOrigin(new Array{allowOrigin}); + } + + static void CreateTestUsers() + { + ApiUser::Ptr user = new ApiUser; + user->SetName("client"); + user->SetClientCN("client"); + user->SetPermissions(new Array{"*"}); + user->Register(); + + user = new ApiUser; + user->SetName("test"); + user->SetPassword("test"); + user->SetPermissions(new Array{"*"}); + user->Register(); + } + + void SetupHttpServerConnection(bool authenticated) + { + String identity = authenticated ? "client" : "invalid"; + m_Connection = new HttpServerConnection(m_WaitGroup, identity, authenticated, server); + m_Connection->Start(); + } + + template + bool AssertServerDisconnected(const std::chrono::duration& timeout) + { + auto iterations = timeout / std::chrono::milliseconds(50); + for (std::size_t i = 0; i < iterations && !m_Connection->Disconnected(); i++) { + Utility::Sleep(std::chrono::duration(timeout).count() / iterations); + } + return m_Connection->Disconnected(); + } +}; + +class UnitTestHandler final : public HttpHandler +{ +public: + using TestFn = std::function; + + static void RegisterTestFn(std::string handle, TestFn fn) { testFns[std::move(handle)] = std::move(fn); } + +private: + bool HandleRequest(const WaitGroup::Ptr&, const HttpRequest& request, HttpResponse& response, + boost::asio::yield_context& yc) override + { + response.result(boost::beast::http::status::ok); + + auto path = request.Url()->GetPath(); + + if (path.size() == 3) { + if (auto it = testFns.find(path[2].GetData()); it != testFns.end()) { + it->second(response, yc); + return true; + } + } + + response.body() << "test"; + return true; + } + + static inline std::unordered_map testFns; +}; + +REGISTER_URLHANDLER("/v1/test", UnitTestHandler); + +BOOST_FIXTURE_TEST_SUITE(remote_httpserverconnection, HttpServerConnectionFixture) + +BOOST_AUTO_TEST_CASE(expect_100_continue) +{ + CreateTestUsers(); + SetupHttpServerConnection(true); + + http::request request; + request.method(http::verb::get); + request.version(11); + request.target("/v1/test"); + request.set(http::field::expect, "100-continue"); + request.set(http::field::host, "localhost:5665"); + request.set(http::field::accept, "application/json"); + request.set(http::field::connection, "close"); + request.content_length(0); + http::request_serializer sr(request); + http::write_header(*client, sr); + client->flush(); + + flat_buffer buf; + http::response response; + BOOST_REQUIRE_NO_THROW(http::read(*client, buf, response)); + + BOOST_REQUIRE_EQUAL(response.version(), 11); + BOOST_REQUIRE_EQUAL(response.result(), http::status::continue_); + + http::write(*client, sr); + client->flush(); + + BOOST_REQUIRE_NO_THROW(http::read(*client, buf, response)); + BOOST_REQUIRE_EQUAL(response.version(), 11); + BOOST_REQUIRE_EQUAL(response.result(), http::status::ok); + BOOST_REQUIRE_EQUAL(response.body(), "test"); + + BOOST_REQUIRE(Shutdown(client)); +} + +BOOST_AUTO_TEST_CASE(bad_request) +{ + CreateTestUsers(); + SetupHttpServerConnection(true); + + http::request request; + request.method(http::verb::get); + request.version(12); + request.target("/v1/test"); + request.set(http::field::host, "localhost:5665"); + request.set(http::field::accept, "application/json"); + request.set(http::field::connection, "close"); + request.content_length(0); + http::write(*client, request); + client->flush(); + + flat_buffer buf; + http::response response; + BOOST_REQUIRE_NO_THROW(http::read(*client, buf, response)); + + BOOST_REQUIRE_EQUAL(response.result(), http::status::bad_request); + BOOST_REQUIRE_NE(response.body().find("

    Bad Request

    "), std::string::npos); + + BOOST_REQUIRE(Shutdown(client)); +} + +BOOST_AUTO_TEST_CASE(error_access_control) +{ + CreateTestUsers(); + CreateApiListener("example.org"); + SetupHttpServerConnection(true); + + http::request request; + request.method(http::verb::options); + request.target("/v1/test"); + request.set(http::field::origin, "example.org"); + request.set(http::field::host, "localhost:5665"); + request.set(http::field::access_control_request_method, "GET"); + request.set(http::field::connection, "close"); + request.content_length(0); + http::write(*client, request); + client->flush(); + + flat_buffer buf; + http::response response; + BOOST_REQUIRE_NO_THROW(http::read(*client, buf, response)); + + BOOST_REQUIRE_EQUAL(response.version(), 11); + BOOST_REQUIRE_EQUAL(response.result(), http::status::ok); + BOOST_REQUIRE_EQUAL(response.body(), "Preflight OK"); + + BOOST_REQUIRE_EQUAL(response[http::field::access_control_allow_credentials], "true"); + BOOST_REQUIRE_EQUAL(response[http::field::access_control_allow_origin], "example.org"); + BOOST_REQUIRE_NE(response[http::field::access_control_allow_methods], ""); + BOOST_REQUIRE_NE(response[http::field::access_control_allow_headers], ""); + + BOOST_REQUIRE(Shutdown(client)); +} + +BOOST_AUTO_TEST_CASE(error_accept_header) +{ + CreateTestUsers(); + SetupHttpServerConnection(true); + + http::request request; + request.method(http::verb::post); + request.target("/v1/test"); + request.set(http::field::host, "localhost:5665"); + request.set(http::field::accept, "text/html"); + request.set(http::field::connection, "close"); + request.content_length(0); + http::write(*client, request); + client->flush(); + + flat_buffer buf; + http::response response; + BOOST_REQUIRE_NO_THROW(http::read(*client, buf, response)); + + BOOST_REQUIRE_EQUAL(response.version(), 11); + BOOST_REQUIRE_EQUAL(response.result(), http::status::bad_request); + BOOST_REQUIRE_EQUAL(response.body(), "

    Accept header is missing or not set to 'application/json'.

    "); + + BOOST_REQUIRE(Shutdown(client)); +} + +BOOST_AUTO_TEST_CASE(authenticate_cn) +{ + CreateTestUsers(); + SetupHttpServerConnection(true); + + http::request request; + request.method(http::verb::get); + request.target("/v1/test"); + request.set(http::field::host, "localhost:5665"); + request.set(http::field::accept, "application/json"); + request.set(http::field::connection, "close"); + request.content_length(0); + http::write(*client, request); + client->flush(); + + flat_buffer buf; + http::response response; + BOOST_REQUIRE_NO_THROW(http::read(*client, buf, response)); + + BOOST_REQUIRE_EQUAL(response.version(), 11); + BOOST_REQUIRE_EQUAL(response.result(), http::status::ok); + + BOOST_REQUIRE(Shutdown(client)); +} + +BOOST_AUTO_TEST_CASE(authenticate_passwd) +{ + CreateTestUsers(); + SetupHttpServerConnection(false); + + http::request request; + request.method(http::verb::get); + request.target("/v1/test"); + request.set(http::field::authorization, "Basic " + Base64::Encode("test:test")); + request.set(http::field::host, "localhost:5665"); + request.set(http::field::accept, "application/json"); + request.set(http::field::connection, "close"); + request.content_length(0); + http::write(*client, request); + client->flush(); + + flat_buffer buf; + http::response response; + BOOST_REQUIRE_NO_THROW(http::read(*client, buf, response)); + + BOOST_REQUIRE_EQUAL(response.version(), 11); + BOOST_REQUIRE_EQUAL(response.result(), http::status::ok); + + BOOST_REQUIRE(Shutdown(client)); +} + +BOOST_AUTO_TEST_CASE(authenticate_error_wronguser) +{ + CreateTestUsers(); + SetupHttpServerConnection(false); + + http::request request; + request.method(http::verb::get); + request.target("/v1/test"); + request.set(http::field::authorization, "Basic " + Base64::Encode("invalid:invalid")); + request.set(http::field::host, "localhost:5665"); + request.set(http::field::accept, "application/json"); + request.set(http::field::connection, "close"); + request.content_length(0); + http::write(*client, request); + client->flush(); + + flat_buffer buf; + http::response response; + BOOST_REQUIRE_NO_THROW(http::read(*client, buf, response)); + + BOOST_REQUIRE_EQUAL(response.version(), 11); + BOOST_REQUIRE_EQUAL(response.result(), http::status::unauthorized); + Dictionary::Ptr body = JsonDecode(response.body()); + BOOST_REQUIRE(body); + BOOST_REQUIRE_EQUAL(body->Get("error"), 401); + + BOOST_REQUIRE(Shutdown(client)); +} + +BOOST_AUTO_TEST_CASE(authenticate_error_wrongpasswd) +{ + CreateTestUsers(); + SetupHttpServerConnection(false); + + http::request request; + request.method(http::verb::get); + request.target("/v1/test"); + request.set(http::field::authorization, "Basic " + Base64::Encode("test:invalid")); + request.set(http::field::host, "localhost:5665"); + request.set(http::field::accept, "application/json"); + request.set(http::field::connection, "close"); + request.content_length(0); + http::write(*client, request); + client->flush(); + + flat_buffer buf; + http::response response; + BOOST_REQUIRE_NO_THROW(http::read(*client, buf, response)); + + BOOST_REQUIRE_EQUAL(response.version(), 11); + BOOST_REQUIRE_EQUAL(response.result(), http::status::unauthorized); + Dictionary::Ptr body = JsonDecode(response.body()); + BOOST_REQUIRE(body); + BOOST_REQUIRE_EQUAL(body->Get("error"), 401); + + BOOST_REQUIRE(Shutdown(client)); +} + +BOOST_AUTO_TEST_CASE(reuse_connection) +{ + CreateTestUsers(); + SetupHttpServerConnection(true); + + http::request request; + request.method(http::verb::get); + request.target("/v1/test"); + request.set(http::field::host, "localhost:5665"); + request.set(http::field::accept, "application/json"); + request.keep_alive(true); + http::write(*client, request); + client->flush(); + + flat_buffer buf; + http::response response; + BOOST_REQUIRE_NO_THROW(http::read(*client, buf, response)); + + BOOST_REQUIRE_EQUAL(response.version(), 11); + BOOST_REQUIRE_EQUAL(response.result(), http::status::ok); + BOOST_REQUIRE_EQUAL(response.body(), "test"); + + request.keep_alive(false); + http::write(*client, request); + client->flush(); + + boost::system::error_code ec; + http::response_parser parser; + BOOST_REQUIRE_NO_THROW(http::read(*client, buf, parser)); + + BOOST_REQUIRE(parser.is_header_done()); + BOOST_REQUIRE(parser.is_done()); + BOOST_REQUIRE_EQUAL(parser.get().version(), 11); + BOOST_REQUIRE_EQUAL(parser.get().result(), http::status::ok); + BOOST_REQUIRE_EQUAL(parser.get().body(), "test"); + + // Second read to get the end of stream error; + http::read(*client, buf, response, ec); + BOOST_REQUIRE_EQUAL(ec, boost::system::error_code{boost::beast::http::error::end_of_stream}); + + BOOST_REQUIRE(AssertServerDisconnected(std::chrono::seconds(5))); + BOOST_REQUIRE(Shutdown(client)); + BOOST_REQUIRE(ExpectLogPattern("HTTP client disconnected .*", std::chrono::seconds(5))); +} + +BOOST_AUTO_TEST_CASE(wg_abort) +{ + CreateTestUsers(); + SetupHttpServerConnection(true); + + UnitTestHandler::RegisterTestFn("wgjoin", [this](HttpResponse& response, const boost::asio::yield_context&) { + response.body() << "test"; + m_WaitGroup->Join(); + }); + + http::request request; + request.method(http::verb::get); + request.target("/v1/test/wgjoin"); + request.set(http::field::host, "localhost:5665"); + request.set(http::field::accept, "application/json"); + request.keep_alive(true); + http::write(*client, request); + client->flush(); + + flat_buffer buf; + http::response_parser parser; + BOOST_REQUIRE_NO_THROW(http::read(*client, buf, parser)); + + BOOST_REQUIRE(parser.is_header_done()); + BOOST_REQUIRE(parser.is_done()); + BOOST_REQUIRE_EQUAL(parser.get().version(), 11); + BOOST_REQUIRE_EQUAL(parser.get().result(), http::status::ok); + BOOST_REQUIRE_EQUAL(parser.get().body(), "test"); + + // Second read to get the end of stream error; + http::response response{}; + boost::system::error_code ec; + http::read(*client, buf, response, ec); + BOOST_REQUIRE_EQUAL(ec, boost::system::error_code{boost::beast::http::error::end_of_stream}); + + BOOST_REQUIRE(AssertServerDisconnected(std::chrono::seconds(5))); + BOOST_REQUIRE(Shutdown(client)); + BOOST_REQUIRE(ExpectLogPattern("HTTP client disconnected .*", std::chrono::seconds(5))); +} + +BOOST_AUTO_TEST_CASE(client_shutdown) +{ + CreateTestUsers(); + SetupHttpServerConnection(true); + + UnitTestHandler::RegisterTestFn("stream", [](HttpResponse& response, const boost::asio::yield_context& yc) { + response.StartStreaming(); + response.Flush(yc); + + boost::asio::deadline_timer dt{IoEngine::Get().GetIoContext()}; + for (;;) { + dt.expires_from_now(boost::posix_time::seconds(1)); + dt.async_wait(yc); + + if (!response.IsClientDisconnected()) { + return; + } + + response.body() << "test"; + response.Flush(yc); + } + }); + + http::request request; + request.method(http::verb::get); + request.target("/v1/test/stream"); + request.set(http::field::host, "localhost:5665"); + request.set(http::field::accept, "application/json"); + request.keep_alive(true); + http::write(*client, request); + client->flush(); + + flat_buffer buf; + http::response_parser parser; + BOOST_REQUIRE_NO_THROW(http::read_header(*client, buf, parser)); + BOOST_REQUIRE(parser.is_header_done()); + + /* Unlike the other test cases we don't require success here, because with the request + * above, UnitTestHandler simulates a HttpHandler that is constantly writing. + * That may cause the shutdown to fail on the client-side with "application data after + * close notify", but the important part is that HttpServerConnection actually closes + * the connection on its own side, which we check with the BOOST_REQUIRE() below. + */ + BOOST_WARN(Shutdown(client)); + + BOOST_REQUIRE(AssertServerDisconnected(std::chrono::seconds(5))); + BOOST_REQUIRE(ExpectLogPattern("HTTP client disconnected .*", std::chrono::seconds(5))); +} + +BOOST_AUTO_TEST_CASE(handler_throw_error) +{ + CreateTestUsers(); + SetupHttpServerConnection(true); + + UnitTestHandler::RegisterTestFn("throw", [](HttpResponse& response, const boost::asio::yield_context&) { + response.StartStreaming(); + response.body() << "test"; + + boost::system::error_code ec{}; + throw boost::system::system_error(ec); + }); + + http::request request; + request.method(http::verb::get); + request.target("/v1/test/throw"); + request.set(http::field::host, "localhost:5665"); + request.set(http::field::accept, "application/json"); + request.keep_alive(false); + http::write(*client, request); + client->flush(); + + flat_buffer buf; + http::response response; + BOOST_REQUIRE_NO_THROW(http::read(*client, buf, response)); + + BOOST_REQUIRE_EQUAL(response.version(), 11); + BOOST_REQUIRE_EQUAL(response.result(), http::status::internal_server_error); + Dictionary::Ptr body = JsonDecode(response.body()); + BOOST_REQUIRE(body); + BOOST_REQUIRE_EQUAL(body->Get("error"), 500); + BOOST_REQUIRE_EQUAL(body->Get("status"), "Unhandled exception"); + + BOOST_REQUIRE(Shutdown(client)); + BOOST_REQUIRE(ExpectLogPattern("HTTP client disconnected .*", std::chrono::seconds(5))); + BOOST_REQUIRE(!ExpectLogPattern("Exception while processing HTTP request.*")); +} + +BOOST_AUTO_TEST_CASE(handler_throw_streaming) +{ + CreateTestUsers(); + SetupHttpServerConnection(true); + + UnitTestHandler::RegisterTestFn("throw", [](HttpResponse& response, const boost::asio::yield_context& yc) { + response.StartStreaming(); + response.body() << "test"; + + response.Flush(yc); + + boost::system::error_code ec{}; + throw boost::system::system_error(ec); + }); + + http::request request; + request.method(http::verb::get); + request.target("/v1/test/throw"); + request.set(http::field::host, "localhost:5665"); + request.set(http::field::accept, "application/json"); + request.keep_alive(true); + + http::write(*client, request); + client->flush(); + + flat_buffer buf; + http::response_parser parser; + boost::system::error_code ec; + http::read(*client, buf, parser, ec); + + /* Since the handler threw in the middle of sending the message we shouldn't be able + * to read a complete message here. + */ + BOOST_REQUIRE_EQUAL(ec, boost::system::error_code{boost::beast::http::error::partial_message}); + + /* The body should only contain the single "test" the handler has written, without any + * attempts made to additionally write some json error message. + */ + BOOST_REQUIRE_EQUAL(parser.get().body(), "test"); + + /* We then expect the server to initiate a shutdown, which we then complete below. + */ + BOOST_REQUIRE(AssertServerDisconnected(std::chrono::seconds(5))); + BOOST_REQUIRE(Shutdown(client)); + BOOST_REQUIRE(ExpectLogPattern("HTTP client disconnected .*", std::chrono::seconds(5))); + BOOST_REQUIRE(ExpectLogPattern("Exception while processing HTTP request.*")); +} + +BOOST_AUTO_TEST_CASE(liveness_disconnect) +{ + SetupHttpServerConnection(false); + + BOOST_REQUIRE(AssertServerDisconnected(std::chrono::seconds(11))); + BOOST_REQUIRE(ExpectLogPattern("HTTP client disconnected .*")); + BOOST_REQUIRE(ExpectLogPattern("No messages for HTTP connection have been received in the last 10 seconds.")); + BOOST_REQUIRE(Shutdown(client)); +} + +BOOST_AUTO_TEST_SUITE_END()