/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */ #include "icinga/checkable.hpp" #include "icinga/host.hpp" #include "icinga/icingaapplication.hpp" #include "icinga/service.hpp" #include "base/dictionary.hpp" #include "base/objectlock.hpp" #include "base/logger.hpp" #include "base/exception.hpp" #include "base/context.hpp" #include "base/convert.hpp" #include "remote/apilistener.hpp" using namespace icinga; boost::signals2::signal&, const NotificationType&, const CheckResult::Ptr&, const String&, const String&, const MessageOrigin::Ptr&)> Checkable::OnNotificationSentToAllUsers; boost::signals2::signal Checkable::OnNotificationSentToUser; void Checkable::ResetNotificationNumbers() { for (const Notification::Ptr& notification : GetNotifications()) { ObjectLock olock(notification); notification->ResetNotificationNumber(); } } void Checkable::SendNotifications(NotificationType type, const CheckResult::Ptr& cr, const String& author, const String& text) { String checkableName = GetName(); CONTEXT("Sending notifications for object '" + checkableName + "'"); bool force = GetForceNextNotification(); SetForceNextNotification(false); if (!IcingaApplication::GetInstance()->GetEnableNotifications() || !GetEnableNotifications()) { if (!force) { Log(LogInformation, "Checkable") << "Notifications are disabled for checkable '" << checkableName << "'."; return; } } std::set notifications = GetNotifications(); String notificationTypeName = Notification::NotificationTypeToString(type); // Bail early if there are no notifications. if (notifications.empty()) { Log(LogNotice, "Checkable") << "Skipping checkable '" << checkableName << "' which doesn't have any notification objects configured."; return; } Log(LogInformation, "Checkable") << "Checkable '" << checkableName << "' has " << notifications.size() << " notification(s). Checking filters for type '" << notificationTypeName << "', sends will be logged."; for (const Notification::Ptr& notification : notifications) { // Re-send stashed notifications from cold startup. if (ApiListener::UpdatedObjectAuthority()) { try { if (!notification->IsPaused()) { auto stashedNotifications (notification->GetStashedNotifications()); if (stashedNotifications->GetLength()) { Log(LogNotice, "Notification") << "Notification '" << notification->GetName() << "': there are some stashed notifications. Stashing notification to preserve order."; stashedNotifications->Add(new Dictionary({ {"type", type}, {"cr", cr}, {"force", force}, {"reminder", false}, {"author", author}, {"text", text} })); } else { notification->BeginExecuteNotification(type, cr, force, false, author, text); } } else { Log(LogNotice, "Notification") << "Notification '" << notification->GetName() << "': HA cluster active, this endpoint does not have the authority (paused=true). Skipping."; } } catch (const std::exception& ex) { Log(LogWarning, "Checkable") << "Exception occurred during notification '" << notification->GetName() << "' for checkable '" << GetName() << "': " << DiagnosticInformation(ex, false); } } else { // Cold startup phase. Stash notification for later. Log(LogNotice, "Notification") << "Notification '" << notification->GetName() << "': object authority hasn't been updated, yet. Stashing notification."; notification->GetStashedNotifications()->Add(new Dictionary({ {"type", type}, {"cr", cr}, {"force", force}, {"reminder", false}, {"author", author}, {"text", text} })); } } } std::set Checkable::GetNotifications() const { boost::mutex::scoped_lock lock(m_NotificationMutex); return m_Notifications; } void Checkable::RegisterNotification(const Notification::Ptr& notification) { boost::mutex::scoped_lock lock(m_NotificationMutex); m_Notifications.insert(notification); } void Checkable::UnregisterNotification(const Notification::Ptr& notification) { boost::mutex::scoped_lock lock(m_NotificationMutex); m_Notifications.erase(notification); } static void FireSuppressedNotifications(Checkable* checkable) { if (!checkable->IsActive()) return; if (checkable->IsPaused()) return; if (!checkable->GetEnableNotifications()) return; int suppressed_types (checkable->GetSuppressedNotifications()); if (!suppressed_types) return; int subtract = 0; for (auto type : {NotificationProblem, NotificationRecovery, NotificationFlappingStart, NotificationFlappingEnd}) { if (suppressed_types & type) { bool still_applies = checkable->NotificationReasonApplies(type); if (still_applies) { bool still_suppressed; switch (type) { case NotificationProblem: /* Fall through. */ case NotificationRecovery: still_suppressed = !checkable->IsReachable(DependencyNotification) || checkable->IsInDowntime() || checkable->IsAcknowledged(); break; case NotificationFlappingStart: /* Fall through. */ case NotificationFlappingEnd: still_suppressed = checkable->IsInDowntime(); break; default: break; } if (!still_suppressed && !checkable->IsLikelyToBeCheckedSoon()) { Checkable::OnNotificationsRequested(checkable, type, checkable->GetLastCheckResult(), "", "", nullptr); subtract |= type; } } else { subtract |= type; } } } if (subtract) { ObjectLock olock (checkable); int suppressed_types_before (checkable->GetSuppressedNotifications()); int suppressed_types_after (suppressed_types_before & ~subtract); if (suppressed_types_after != suppressed_types_before) { checkable->SetSuppressedNotifications(suppressed_types_after); } } } /** * Re-sends all notifications previously suppressed by e.g. downtimes if the notification reason still applies. */ void Checkable::FireSuppressedNotifications(const Timer * const&) { for (auto& host : ConfigType::GetObjectsByType()) { ::FireSuppressedNotifications(host.get()); } for (auto& service : ConfigType::GetObjectsByType()) { ::FireSuppressedNotifications(service.get()); } } /** * Returns whether sending a notification of type type right now would represent *this' current state correctly. * * @param type The type of notification to send (or not to send). * * @return Whether to send the notification. */ bool Checkable::NotificationReasonApplies(NotificationType type) { switch (type) { case NotificationProblem: { auto cr (GetLastCheckResult()); return cr && !IsStateOK(cr->GetState()) && GetStateType() == StateTypeHard; } case NotificationRecovery: { auto cr (GetLastCheckResult()); return cr && IsStateOK(cr->GetState()); } case NotificationFlappingStart: return IsFlapping(); case NotificationFlappingEnd: return !IsFlapping(); default: VERIFY(!"Checkable#NotificationReasonStillApplies(): given type not implemented"); return false; } } /** * E.g. we're going to re-send a stashed problem notification as *this is still not ok. * But if the next check result recovers *this soon, we would send a recovery notification soon after the problem one. * This is not desired, especially for lots of checkables at once. * Because of that if there's likely to be a check result soon, * we delay the re-sending of the stashed notification until the next check. * That check either doesn't change anything and we finally re-send the stashed problem notification * or recovers *this and we drop the stashed notification. * * @return Whether *this is likely to be checked soon */ bool Checkable::IsLikelyToBeCheckedSoon() { if (!GetEnableActiveChecks()) { return false; } // One minute unless the check interval is too short so the next check will always run during the next minute. auto threshold (GetCheckInterval() - 10); if (threshold > 60) { threshold = 60; } else if (threshold < 0) { threshold = 0; } return GetNextCheck() <= Utility::GetTime() + threshold; }