From 5d11df1abfcebbf71cc717877905ac750b231a0d Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Tue, 20 May 2025 16:54:19 +0200 Subject: [PATCH 1/8] 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 2/8] 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 3/8] 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 4/8] 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 5/8] 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 6/8] 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 7/8] 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 8/8] 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."); } /**