diff --git a/doc/19-technical-concepts.md b/doc/19-technical-concepts.md index 470e820c456..c3666fae0c8 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::ClearLastNotifiedStates + +> Location: `clusterevents.cpp` + +##### Message Body + +Key | Value +----------|--------- +jsonrpc | 2.0 +method | event::ClearLastNotifiedStates +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::OnLastNotifiedStatesCleared` +Event Receiver: `LastNotifiedStatesClearedAPIHandler` + +##### 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 2eac7243414..d8829b6281d 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(UpdateLastNotifiedStateOfUser, event, &ClusterEvents::LastNotifiedStateOfUserUpdatedAPIHandler); +REGISTER_APIFUNCTION(ClearLastNotifiedStates, event, &ClusterEvents::LastNotifiedStatesClearedAPIHandler); 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::OnLastNotifiedStateOfUserUpdated.connect(&ClusterEvents::LastNotifiedStateOfUserUpdatedHandler); + Notification::OnLastNotifiedStatesCleared.connect(&ClusterEvents::LastNotifiedStatesClearedHandler); Checkable::OnForceNextCheckChanged.connect(&ClusterEvents::ForceNextCheckChangedHandler); Checkable::OnForceNextNotificationChanged.connect(&ClusterEvents::ForceNextNotificationChangedHandler); Checkable::OnNotificationsRequested.connect(&ClusterEvents::SendNotificationsHandler); @@ -589,6 +591,57 @@ Value ClusterEvents::LastNotifiedStateOfUserUpdatedAPIHandler(const MessageOrigi return Empty; } +void ClusterEvents::LastNotifiedStatesClearedHandler(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::ClearLastNotifiedStates"); + message->Set("params", params); + + listener->RelayMessage(origin, notification, message, true); +} + +Value ClusterEvents::LastNotifiedStatesClearedAPIHandler(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::OnLastNotifiedStatesCleared(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 0d67681b24e..2cb3a3a277f 100644 --- a/lib/icinga/clusterevents.hpp +++ b/lib/icinga/clusterevents.hpp @@ -44,6 +44,9 @@ class ClusterEvents static void LastNotifiedStateOfUserUpdatedHandler(const Notification::Ptr& notification, const String& user, uint_fast8_t state, const MessageOrigin::Ptr& origin); static Value LastNotifiedStateOfUserUpdatedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params); + static void LastNotifiedStatesClearedHandler(const Notification::Ptr& notification, const MessageOrigin::Ptr& origin); + static Value LastNotifiedStatesClearedAPIHandler(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 4f5e71a94e3..0d011cb3d72 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::OnLastNotifiedStateOfUserUpdated; +boost::signals2::signal Notification::OnLastNotifiedStatesCleared; 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(); + OnLastNotifiedStatesCleared(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); - OnLastNotifiedStateOfUserUpdated(this, userName, state, nullptr); - } + if (state != (uint_fast8_t)GetLastNotifiedStatePerUser()->Get(userName)) { + GetLastNotifiedStatePerUser()->Set(userName, state); + OnLastNotifiedStateOfUserUpdated(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 f3e89018bd1..01446801838 100644 --- a/lib/icinga/notification.hpp +++ b/lib/icinga/notification.hpp @@ -94,6 +94,7 @@ class Notification final : public ObjectImpl static boost::signals2::signal OnNextNotificationChanged; static boost::signals2::signal OnLastNotifiedStateOfUserUpdated; + static boost::signals2::signal OnLastNotifiedStatesCleared; void Validate(int types, const ValidationUtils& utils) override; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index e0b76689578..c2600bf420b 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -140,6 +140,7 @@ add_boost_test(base icinga_notification/type_filter icinga_notification/duplicate_due_to_filter icinga_notification/duplicate_due_to_filter_volatile + icinga_notification/duplicate_due_to_filter_recovery icinga_macros/simple icinga_legacytimeperiod/simple icinga_legacytimeperiod/advanced diff --git a/test/icinga-notification.cpp b/test/icinga-notification.cpp index 24d0ccf5b1d..9a0e0be748c 100644 --- a/test/icinga-notification.cpp +++ b/test/icinga-notification.cpp @@ -180,4 +180,15 @@ BOOST_AUTO_TEST_CASE(duplicate_due_to_filter_volatile) helper.SendStateNotification(NotificationProblem, ServiceCritical, true); } +BOOST_AUTO_TEST_CASE(duplicate_due_to_filter_recovery) +{ + DuplicateDueToFilterHelper helper; + + helper.SendStateNotification(NotificationProblem, ServiceCritical, true); + helper.SendStateNotification(NotificationProblem, ServiceWarning, false); + helper.SendStateNotification(NotificationProblem, ServiceCritical, false); + helper.SendStateNotification(NotificationRecovery, ServiceOK, false); + helper.SendStateNotification(NotificationProblem, ServiceCritical, true); +} + BOOST_AUTO_TEST_SUITE_END()