diff --git a/doc/19-technical-concepts.md b/doc/19-technical-concepts.md index 74a2756ca..b3a056170 100644 --- a/doc/19-technical-concepts.md +++ b/doc/19-technical-concepts.md @@ -1550,6 +1550,40 @@ Message updates will be dropped when: * Notification does not exist. * Origin endpoint is not within the local zone. +#### event::ClearLastNotifiedStatePerUser + +> Location: `clusterevents.cpp` + +##### Message Body + +Key | Value +----------|--------- +jsonrpc | 2.0 +method | event::ClearLastNotifiedStatePerUser +params | Dictionary + +##### Params + +Key | Type | Description +-------------|--------|------------------ +notification | String | Notification name + +Used to sync the state of a notification object within the same HA zone. + +##### Functions + +Event Sender: `Notification::OnLastNotifiedStatePerUserCleared` +Event Receiver: `LastNotifiedStatePerUserClearedAPIHandler` + +##### Permissions + +The receiver will not process messages from not configured endpoints. + +Message updates will be dropped when: + +* Notification does not exist. +* Origin endpoint is not within the local zone. + #### event::SetForceNextCheck > Location: `clusterevents.cpp` diff --git a/lib/icinga/clusterevents.cpp b/lib/icinga/clusterevents.cpp index 95616d2e3..fe5167b78 100644 --- a/lib/icinga/clusterevents.cpp +++ b/lib/icinga/clusterevents.cpp @@ -30,6 +30,7 @@ REGISTER_APIFUNCTION(SetSuppressedNotifications, event, &ClusterEvents::Suppress REGISTER_APIFUNCTION(SetSuppressedNotificationTypes, event, &ClusterEvents::SuppressedNotificationTypesChangedAPIHandler); REGISTER_APIFUNCTION(SetNextNotification, event, &ClusterEvents::NextNotificationChangedAPIHandler); REGISTER_APIFUNCTION(UpdateLastNotifiedStatePerUser, event, &ClusterEvents::LastNotifiedStatePerUserUpdatedAPIHandler); +REGISTER_APIFUNCTION(ClearLastNotifiedStatePerUser, event, &ClusterEvents::LastNotifiedStatePerUserClearedAPIHandler); REGISTER_APIFUNCTION(SetForceNextCheck, event, &ClusterEvents::ForceNextCheckChangedAPIHandler); REGISTER_APIFUNCTION(SetForceNextNotification, event, &ClusterEvents::ForceNextNotificationChangedAPIHandler); REGISTER_APIFUNCTION(SetAcknowledgement, event, &ClusterEvents::AcknowledgementSetAPIHandler); @@ -52,6 +53,7 @@ void ClusterEvents::StaticInitialize() Notification::OnSuppressedNotificationsChanged.connect(&ClusterEvents::SuppressedNotificationTypesChangedHandler); Notification::OnNextNotificationChanged.connect(&ClusterEvents::NextNotificationChangedHandler); Notification::OnLastNotifiedStatePerUserUpdated.connect(&ClusterEvents::LastNotifiedStatePerUserUpdatedHandler); + Notification::OnLastNotifiedStatePerUserCleared.connect(&ClusterEvents::LastNotifiedStatePerUserClearedHandler); Checkable::OnForceNextCheckChanged.connect(&ClusterEvents::ForceNextCheckChangedHandler); Checkable::OnForceNextNotificationChanged.connect(&ClusterEvents::ForceNextNotificationChangedHandler); Checkable::OnNotificationsRequested.connect(&ClusterEvents::SendNotificationsHandler); @@ -589,6 +591,57 @@ Value ClusterEvents::LastNotifiedStatePerUserUpdatedAPIHandler(const MessageOrig return Empty; } +void ClusterEvents::LastNotifiedStatePerUserClearedHandler(const Notification::Ptr& notification, const MessageOrigin::Ptr& origin) +{ + auto listener (ApiListener::GetInstance()); + + if (!listener) { + return; + } + + Dictionary::Ptr params = new Dictionary(); + params->Set("notification", notification->GetName()); + + Dictionary::Ptr message = new Dictionary(); + message->Set("jsonrpc", "2.0"); + message->Set("method", "event::ClearLastNotifiedStatePerUser"); + message->Set("params", params); + + listener->RelayMessage(origin, notification, message, true); +} + +Value ClusterEvents::LastNotifiedStatePerUserClearedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params) +{ + auto endpoint (origin->FromClient->GetEndpoint()); + + if (!endpoint) { + Log(LogNotice, "ClusterEvents") + << "Discarding 'last notified state of user cleared' message from '" + << origin->FromClient->GetIdentity() << "': Invalid endpoint origin (client not allowed)."; + + return Empty; + } + + if (origin->FromZone && origin->FromZone != Zone::GetLocalZone()) { + Log(LogNotice, "ClusterEvents") + << "Discarding 'last notified state of user cleared' message from '" + << origin->FromClient->GetIdentity() << "': Unauthorized access."; + + return Empty; + } + + auto notification (Notification::GetByName(params->Get("notification"))); + + if (!notification) { + return Empty; + } + + notification->GetLastNotifiedStatePerUser()->Clear(); + Notification::OnLastNotifiedStatePerUserCleared(notification, origin); + + return Empty; +} + void ClusterEvents::ForceNextCheckChangedHandler(const Checkable::Ptr& checkable, const MessageOrigin::Ptr& origin) { ApiListener::Ptr listener = ApiListener::GetInstance(); diff --git a/lib/icinga/clusterevents.hpp b/lib/icinga/clusterevents.hpp index 2e6e092f2..8daf86ab3 100644 --- a/lib/icinga/clusterevents.hpp +++ b/lib/icinga/clusterevents.hpp @@ -44,6 +44,9 @@ public: static void LastNotifiedStatePerUserUpdatedHandler(const Notification::Ptr& notification, const String& user, uint_fast8_t state, const MessageOrigin::Ptr& origin); static Value LastNotifiedStatePerUserUpdatedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params); + static void LastNotifiedStatePerUserClearedHandler(const Notification::Ptr& notification, const MessageOrigin::Ptr& origin); + static Value LastNotifiedStatePerUserClearedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params); + static void ForceNextCheckChangedHandler(const Checkable::Ptr& checkable, const MessageOrigin::Ptr& origin); static Value ForceNextCheckChangedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params); diff --git a/lib/icinga/notification.cpp b/lib/icinga/notification.cpp index 9533d229b..ab8d42b8c 100644 --- a/lib/icinga/notification.cpp +++ b/lib/icinga/notification.cpp @@ -24,6 +24,7 @@ std::map Notification::m_TypeFilterMap; boost::signals2::signal Notification::OnNextNotificationChanged; boost::signals2::signal Notification::OnLastNotifiedStatePerUserUpdated; +boost::signals2::signal Notification::OnLastNotifiedStatePerUserCleared; String NotificationNameComposer::MakeName(const String& shortName, const Object::Ptr& context) const { @@ -232,6 +233,13 @@ void Notification::BeginExecuteNotification(NotificationType type, const CheckRe << "notifications of type '" << notificationTypeName << "' for notification object '" << notificationName << "'."; + if (type == NotificationRecovery) { + auto states (GetLastNotifiedStatePerUser()); + + states->Clear(); + OnLastNotifiedStatePerUserCleared(this, nullptr); + } + Checkable::Ptr checkable = GetCheckable(); if (!force) { @@ -469,19 +477,14 @@ void Notification::BeginExecuteNotification(NotificationType type, const CheckRe /* collect all notified users */ allNotifiedUsers.insert(user); - switch (type) { - case NotificationProblem: - case NotificationRecovery: { - auto [host, service] = GetHostService(checkable); - uint_fast8_t state = service ? service->GetState() : host->GetState(); + if (type == NotificationProblem) { + auto [host, service] = GetHostService(checkable); + uint_fast8_t state = service ? service->GetState() : host->GetState(); - if (state != (uint_fast8_t)GetLastNotifiedStatePerUser()->Get(userName)) { - GetLastNotifiedStatePerUser()->Set(userName, state); - OnLastNotifiedStatePerUserUpdated(this, userName, state, nullptr); - } + if (state != (uint_fast8_t)GetLastNotifiedStatePerUser()->Get(userName)) { + GetLastNotifiedStatePerUser()->Set(userName, state); + OnLastNotifiedStatePerUserUpdated(this, userName, state, nullptr); } - default: - ; } /* store all notified users for later recovery checks */ diff --git a/lib/icinga/notification.hpp b/lib/icinga/notification.hpp index 2c41a13e7..1b6cbedb1 100644 --- a/lib/icinga/notification.hpp +++ b/lib/icinga/notification.hpp @@ -94,6 +94,7 @@ public: static boost::signals2::signal OnNextNotificationChanged; static boost::signals2::signal OnLastNotifiedStatePerUserUpdated; + static boost::signals2::signal OnLastNotifiedStatePerUserCleared; void Validate(int types, const ValidationUtils& utils) override; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 5e054c48e..24eb2198c 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -141,6 +141,8 @@ add_boost_test(base icinga_notification/no_filter_problem_no_duplicate icinga_notification/filter_problem_no_duplicate icinga_notification/volatile_filter_problem_duplicate + icinga_notification/no_recovery_filter_no_duplicate + icinga_notification/recovery_filter_duplicate icinga_macros/simple icinga_legacytimeperiod/simple icinga_legacytimeperiod/advanced diff --git a/test/icinga-notification.cpp b/test/icinga-notification.cpp index 4b3fe5f73..a0aeb7df8 100644 --- a/test/icinga-notification.cpp +++ b/test/icinga-notification.cpp @@ -194,4 +194,22 @@ BOOST_AUTO_TEST_CASE(volatile_filter_problem_duplicate) helper.SendStateNotification(ServiceCritical, true); } +BOOST_AUTO_TEST_CASE(no_recovery_filter_no_duplicate) +{ + DuplicateDueToFilterHelper helper (~0, ~0); + + helper.SendStateNotification(ServiceCritical, true); + helper.SendStateNotification(ServiceOK, true); + helper.SendStateNotification(ServiceCritical, true); +} + +BOOST_AUTO_TEST_CASE(recovery_filter_duplicate) +{ + DuplicateDueToFilterHelper helper (~NotificationRecovery, ~0); + + helper.SendStateNotification(ServiceCritical, true); + helper.SendStateNotification(ServiceOK, false); + helper.SendStateNotification(ServiceCritical, true); +} + BOOST_AUTO_TEST_SUITE_END()