diff --git a/doc/19-technical-concepts.md b/doc/19-technical-concepts.md index 1da32b6f2..74a2756ca 100644 --- a/doc/19-technical-concepts.md +++ b/doc/19-technical-concepts.md @@ -1514,6 +1514,42 @@ Message updates will be dropped when: * Notification does not exist. * Origin endpoint's zone is not allowed to access this checkable. +#### event::UpdateLastNotifiedStatePerUser + +> Location: `clusterevents.cpp` + +##### Message Body + +Key | Value +----------|--------- +jsonrpc | 2.0 +method | event::UpdateLastNotifiedStatePerUser +params | Dictionary + +##### Params + +Key | Type | Description +-------------|--------|------------------ +notification | String | Notification name +user | String | User name +state | Number | Checkable state the user just got a problem notification for + +Used to sync the state of a notification object within the same HA zone. + +##### Functions + +Event Sender: `Notification::OnLastNotifiedStatePerUserUpdated` +Event Receiver: `LastNotifiedStatePerUserUpdatedAPIHandler` + +##### 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 6c4daa8b8..95616d2e3 100644 --- a/lib/icinga/clusterevents.cpp +++ b/lib/icinga/clusterevents.cpp @@ -29,6 +29,7 @@ REGISTER_APIFUNCTION(SetStateBeforeSuppression, event, &ClusterEvents::StateBefo REGISTER_APIFUNCTION(SetSuppressedNotifications, event, &ClusterEvents::SuppressedNotificationsChangedAPIHandler); REGISTER_APIFUNCTION(SetSuppressedNotificationTypes, event, &ClusterEvents::SuppressedNotificationTypesChangedAPIHandler); REGISTER_APIFUNCTION(SetNextNotification, event, &ClusterEvents::NextNotificationChangedAPIHandler); +REGISTER_APIFUNCTION(UpdateLastNotifiedStatePerUser, event, &ClusterEvents::LastNotifiedStatePerUserUpdatedAPIHandler); REGISTER_APIFUNCTION(SetForceNextCheck, event, &ClusterEvents::ForceNextCheckChangedAPIHandler); REGISTER_APIFUNCTION(SetForceNextNotification, event, &ClusterEvents::ForceNextNotificationChangedAPIHandler); REGISTER_APIFUNCTION(SetAcknowledgement, event, &ClusterEvents::AcknowledgementSetAPIHandler); @@ -50,6 +51,7 @@ void ClusterEvents::StaticInitialize() Checkable::OnSuppressedNotificationsChanged.connect(&ClusterEvents::SuppressedNotificationsChangedHandler); Notification::OnSuppressedNotificationsChanged.connect(&ClusterEvents::SuppressedNotificationTypesChangedHandler); Notification::OnNextNotificationChanged.connect(&ClusterEvents::NextNotificationChangedHandler); + Notification::OnLastNotifiedStatePerUserUpdated.connect(&ClusterEvents::LastNotifiedStatePerUserUpdatedHandler); Checkable::OnForceNextCheckChanged.connect(&ClusterEvents::ForceNextCheckChangedHandler); Checkable::OnForceNextNotificationChanged.connect(&ClusterEvents::ForceNextNotificationChangedHandler); Checkable::OnNotificationsRequested.connect(&ClusterEvents::SendNotificationsHandler); @@ -529,6 +531,64 @@ Value ClusterEvents::NextNotificationChangedAPIHandler(const MessageOrigin::Ptr& return Empty; } +void ClusterEvents::LastNotifiedStatePerUserUpdatedHandler(const Notification::Ptr& notification, const String& user, uint_fast8_t state, const MessageOrigin::Ptr& origin) +{ + auto listener (ApiListener::GetInstance()); + + if (!listener) { + return; + } + + Dictionary::Ptr params = new Dictionary(); + params->Set("notification", notification->GetName()); + params->Set("user", user); + params->Set("state", state); + + Dictionary::Ptr message = new Dictionary(); + message->Set("jsonrpc", "2.0"); + message->Set("method", "event::UpdateLastNotifiedStatePerUser"); + message->Set("params", params); + + listener->RelayMessage(origin, notification, message, true); +} + +Value ClusterEvents::LastNotifiedStatePerUserUpdatedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params) +{ + auto endpoint (origin->FromClient->GetEndpoint()); + + if (!endpoint) { + Log(LogNotice, "ClusterEvents") + << "Discarding 'last notified state of user updated' 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 updated' message from '" + << origin->FromClient->GetIdentity() << "': Unauthorized access."; + + return Empty; + } + + auto notification (Notification::GetByName(params->Get("notification"))); + + if (!notification) { + return Empty; + } + + auto state (params->Get("state")); + + if (!state.IsNumber()) { + return Empty; + } + + notification->GetLastNotifiedStatePerUser()->Set(params->Get("user"), state); + Notification::OnLastNotifiedStatePerUserUpdated(notification, params->Get("user"), state, 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 4cdadacc9..2e6e092f2 100644 --- a/lib/icinga/clusterevents.hpp +++ b/lib/icinga/clusterevents.hpp @@ -41,6 +41,9 @@ public: static void NextNotificationChangedHandler(const Notification::Ptr& notification, const MessageOrigin::Ptr& origin); static Value NextNotificationChangedAPIHandler(const MessageOrigin::Ptr& origin, const Dictionary::Ptr& params); + 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 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 05628969c..8a254b8fc 100644 --- a/lib/icinga/notification.cpp +++ b/lib/icinga/notification.cpp @@ -23,6 +23,7 @@ std::map Notification::m_StateFilterMap; std::map Notification::m_TypeFilterMap; boost::signals2::signal Notification::OnNextNotificationChanged; +boost::signals2::signal Notification::OnLastNotifiedStatePerUserUpdated; String NotificationNameComposer::MakeName(const String& shortName, const Object::Ptr& context) const { @@ -456,7 +457,12 @@ void Notification::BeginExecuteNotification(NotificationType type, const CheckRe case NotificationProblem: case NotificationRecovery: { auto [host, service] = GetHostService(checkable); - GetLastNotifiedStatePerUser()->Set(userName, service ? service->GetState() : host->GetState()); + 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); + } } default: ; diff --git a/lib/icinga/notification.hpp b/lib/icinga/notification.hpp index ec9164f19..1c6a2f44d 100644 --- a/lib/icinga/notification.hpp +++ b/lib/icinga/notification.hpp @@ -13,6 +13,7 @@ #include "remote/endpoint.hpp" #include "remote/messageorigin.hpp" #include "base/array.hpp" +#include namespace icinga { @@ -92,6 +93,7 @@ public: static String NotificationHostStateToString(HostState state); static boost::signals2::signal OnNextNotificationChanged; + static boost::signals2::signal OnLastNotifiedStatePerUserUpdated; void Validate(int types, const ValidationUtils& utils) override;