1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
3 #include "icinga/notification.hpp"
4 #include "icinga/notification-ti.cpp"
5 #include "icinga/notificationcommand.hpp"
6 #include "icinga/service.hpp"
7 #include "remote/apilistener.hpp"
8 #include "base/objectlock.hpp"
9 #include "base/logger.hpp"
10 #include "base/utility.hpp"
11 #include "base/convert.hpp"
12 #include "base/exception.hpp"
13 #include "base/initialize.hpp"
14 #include "base/scriptglobal.hpp"
17 using namespace icinga;
19 REGISTER_TYPE(Notification);
20 INITIALIZE_ONCE(&Notification::StaticInitialize);
22 std::map<String, int> Notification::m_StateFilterMap;
23 std::map<String, int> Notification::m_TypeFilterMap;
25 boost::signals2::signal<void (const Notification::Ptr&, const MessageOrigin::Ptr&)> Notification::OnNextNotificationChanged;
26 boost::signals2::signal<void (const Notification::Ptr&, const NotificationResult::Ptr&, const MessageOrigin::Ptr&)> Notification::OnNewNotificationResult;
28 String NotificationNameComposer::MakeName(const String& shortName, const Object::Ptr& context) const
30 Notification::Ptr notification = dynamic_pointer_cast<Notification>(context);
35 String name = notification->GetHostName();
37 if (!notification->GetServiceName().IsEmpty())
38 name += "!" + notification->GetServiceName();
40 name += "!" + shortName;
45 Dictionary::Ptr NotificationNameComposer::ParseName(const String& name) const
47 std::vector<String> tokens = name.Split("!");
49 if (tokens.size() < 2)
50 BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid Notification name."));
52 Dictionary::Ptr result = new Dictionary();
53 result->Set("host_name", tokens[0]);
55 if (tokens.size() > 2) {
56 result->Set("service_name", tokens[1]);
57 result->Set("name", tokens[2]);
59 result->Set("name", tokens[1]);
65 void Notification::StaticInitialize()
67 ScriptGlobal::Set("Icinga.OK", "OK", true);
68 ScriptGlobal::Set("Icinga.Warning", "Warning", true);
69 ScriptGlobal::Set("Icinga.Critical", "Critical", true);
70 ScriptGlobal::Set("Icinga.Unknown", "Unknown", true);
71 ScriptGlobal::Set("Icinga.Up", "Up", true);
72 ScriptGlobal::Set("Icinga.Down", "Down", true);
74 ScriptGlobal::Set("Icinga.DowntimeStart", "DowntimeStart", true);
75 ScriptGlobal::Set("Icinga.DowntimeEnd", "DowntimeEnd", true);
76 ScriptGlobal::Set("Icinga.DowntimeRemoved", "DowntimeRemoved", true);
77 ScriptGlobal::Set("Icinga.Custom", "Custom", true);
78 ScriptGlobal::Set("Icinga.Acknowledgement", "Acknowledgement", true);
79 ScriptGlobal::Set("Icinga.Problem", "Problem", true);
80 ScriptGlobal::Set("Icinga.Recovery", "Recovery", true);
81 ScriptGlobal::Set("Icinga.FlappingStart", "FlappingStart", true);
82 ScriptGlobal::Set("Icinga.FlappingEnd", "FlappingEnd", true);
84 m_StateFilterMap["OK"] = StateFilterOK;
85 m_StateFilterMap["Warning"] = StateFilterWarning;
86 m_StateFilterMap["Critical"] = StateFilterCritical;
87 m_StateFilterMap["Unknown"] = StateFilterUnknown;
88 m_StateFilterMap["Up"] = StateFilterUp;
89 m_StateFilterMap["Down"] = StateFilterDown;
91 m_TypeFilterMap["DowntimeStart"] = NotificationDowntimeStart;
92 m_TypeFilterMap["DowntimeEnd"] = NotificationDowntimeEnd;
93 m_TypeFilterMap["DowntimeRemoved"] = NotificationDowntimeRemoved;
94 m_TypeFilterMap["Custom"] = NotificationCustom;
95 m_TypeFilterMap["Acknowledgement"] = NotificationAcknowledgement;
96 m_TypeFilterMap["Problem"] = NotificationProblem;
97 m_TypeFilterMap["Recovery"] = NotificationRecovery;
98 m_TypeFilterMap["FlappingStart"] = NotificationFlappingStart;
99 m_TypeFilterMap["FlappingEnd"] = NotificationFlappingEnd;
102 void Notification::OnConfigLoaded()
104 ObjectImpl<Notification>::OnConfigLoaded();
106 SetTypeFilter(FilterArrayToInt(GetTypes(), GetTypeFilterMap(), ~0));
107 SetStateFilter(FilterArrayToInt(GetStates(), GetStateFilterMap(), ~0));
110 void Notification::OnAllConfigLoaded()
112 ObjectImpl<Notification>::OnAllConfigLoaded();
114 Host::Ptr host = Host::GetByName(GetHostName());
116 if (GetServiceName().IsEmpty())
119 m_Checkable = host->GetServiceByShortName(GetServiceName());
122 BOOST_THROW_EXCEPTION(ScriptError("Notification object refers to a host/service which doesn't exist.", GetDebugInfo()));
124 GetCheckable()->RegisterNotification(this);
127 void Notification::Start(bool runtimeCreated)
129 Checkable::Ptr obj = GetCheckable();
132 obj->RegisterNotification(this);
134 if (ApiListener::IsHACluster() && GetNextNotification() < Utility::GetTime() + 60)
135 SetNextNotification(Utility::GetTime() + 60, true);
137 ObjectImpl<Notification>::Start(runtimeCreated);
140 void Notification::Stop(bool runtimeRemoved)
142 ObjectImpl<Notification>::Stop(runtimeRemoved);
144 Checkable::Ptr obj = GetCheckable();
147 obj->UnregisterNotification(this);
150 Checkable::Ptr Notification::GetCheckable() const
152 return static_pointer_cast<Checkable>(m_Checkable);
155 NotificationCommand::Ptr Notification::GetCommand() const
157 return NotificationCommand::GetByName(GetCommandRaw());
160 std::set<User::Ptr> Notification::GetUsers() const
162 std::set<User::Ptr> result;
164 Array::Ptr users = GetUsersRaw();
167 ObjectLock olock(users);
169 for (const String& name : users) {
170 User::Ptr user = User::GetByName(name);
182 std::set<UserGroup::Ptr> Notification::GetUserGroups() const
184 std::set<UserGroup::Ptr> result;
186 Array::Ptr groups = GetUserGroupsRaw();
189 ObjectLock olock(groups);
191 for (const String& name : groups) {
192 UserGroup::Ptr ug = UserGroup::GetByName(name);
204 TimePeriod::Ptr Notification::GetPeriod() const
206 return TimePeriod::GetByName(GetPeriodRaw());
209 void Notification::UpdateNotificationNumber()
211 SetNotificationNumber(GetNotificationNumber() + 1);
214 void Notification::ResetNotificationNumber()
216 SetNotificationNumber(0);
219 void Notification::BeginExecuteNotification(NotificationType type, const CheckResult::Ptr& cr, bool force, bool reminder, const String& author, const String& text)
221 String notificationName = GetName();
222 String notificationTypeName = NotificationTypeToString(type);
224 Log(LogNotice, "Notification")
225 << "Attempting to send " << (reminder ? "reminder " : "")
226 << "notifications of type '" << notificationTypeName
227 << "' for notification object '" << notificationName << "'.";
229 Checkable::Ptr checkable = GetCheckable();
232 TimePeriod::Ptr tp = GetPeriod();
234 if (tp && !tp->IsInside(Utility::GetTime())) {
235 Log(LogNotice, "Notification")
236 << "Not sending " << (reminder ? "reminder " : "") << "notifications for notification object '" << notificationName
237 << "': not in timeperiod '" << tp->GetName() << "'";
241 double now = Utility::GetTime();
242 Dictionary::Ptr times = GetTimes();
244 if (times && type == NotificationProblem) {
245 Value timesBegin = times->Get("begin");
246 Value timesEnd = times->Get("end");
248 if (timesBegin != Empty && timesBegin >= 0 && now < checkable->GetLastHardStateChange() + timesBegin) {
249 Log(LogNotice, "Notification")
250 << "Not sending " << (reminder ? "reminder " : "") << "notifications for notification object '"
251 << notificationName << "': before specified begin time (" << Utility::FormatDuration(timesBegin) << ")";
253 /* we need to adjust the next notification time
254 * delaying the first notification
256 SetNextNotification(checkable->GetLastHardStateChange() + timesBegin + 1.0);
261 if (timesEnd != Empty && timesEnd >= 0 && now > checkable->GetLastHardStateChange() + timesEnd) {
262 Log(LogNotice, "Notification")
263 << "Not sending " << (reminder ? "reminder " : "") << "notifications for notification object '"
264 << notificationName << "': after specified end time (" << Utility::FormatDuration(timesEnd) << ")";
269 unsigned long ftype = type;
271 Log(LogDebug, "Notification")
272 << "Type '" << NotificationTypeToString(type)
273 << "', TypeFilter: " << NotificationFilterToString(GetTypeFilter(), GetTypeFilterMap())
274 << " (FType=" << ftype << ", TypeFilter=" << GetTypeFilter() << ")";
276 if (!(ftype & GetTypeFilter())) {
277 Log(LogNotice, "Notification")
278 << "Not sending " << (reminder ? "reminder " : "") << "notifications for notification object '"
279 << notificationName << "': type '"
280 << NotificationTypeToString(type) << "' does not match type filter: "
281 << NotificationFilterToString(GetTypeFilter(), GetTypeFilterMap()) << ".";
283 /* Ensure to reset no_more_notifications on Recovery notifications,
284 * even if the admin did not configure them in the filter.
287 ObjectLock olock(this);
288 if (type == NotificationRecovery && GetInterval() <= 0)
289 SetNoMoreNotifications(false);
295 /* Check state filters for problem notifications. Recovery notifications will be filtered away later. */
296 if (type == NotificationProblem) {
298 Service::Ptr service;
299 tie(host, service) = GetHostService(checkable);
301 unsigned long fstate;
305 fstate = ServiceStateToFilter(service->GetState());
306 stateStr = NotificationServiceStateToString(service->GetState());
308 fstate = HostStateToFilter(host->GetState());
309 stateStr = NotificationHostStateToString(host->GetState());
312 Log(LogDebug, "Notification")
313 << "State '" << stateStr << "', StateFilter: " << NotificationFilterToString(GetStateFilter(), GetStateFilterMap())
314 << " (FState=" << fstate << ", StateFilter=" << GetStateFilter() << ")";
316 if (!(fstate & GetStateFilter())) {
317 Log(LogNotice, "Notification")
318 << "Not sending " << (reminder ? "reminder " : "") << "notifications for notification object '"
319 << notificationName << "': state '" << stateStr
320 << "' does not match state filter: " << NotificationFilterToString(GetStateFilter(), GetStateFilterMap()) << ".";
325 Log(LogNotice, "Notification")
326 << "Not checking " << (reminder ? "reminder " : "") << "notification filters for notification object '"
327 << notificationName << "': Notification was forced.";
331 ObjectLock olock(this);
333 UpdateNotificationNumber();
334 double now = Utility::GetTime();
335 SetLastNotification(now);
337 if (type == NotificationProblem && GetInterval() <= 0)
338 SetNoMoreNotifications(true);
340 SetNoMoreNotifications(false);
342 if (type == NotificationProblem && GetInterval() > 0)
343 SetNextNotification(now + GetInterval());
345 if (type == NotificationProblem)
346 SetLastProblemNotification(now);
349 std::set<User::Ptr> allUsers;
351 std::set<User::Ptr> users = GetUsers();
352 std::copy(users.begin(), users.end(), std::inserter(allUsers, allUsers.begin()));
354 for (const UserGroup::Ptr& ug : GetUserGroups()) {
355 std::set<User::Ptr> members = ug->GetMembers();
356 std::copy(members.begin(), members.end(), std::inserter(allUsers, allUsers.begin()));
359 std::set<User::Ptr> allNotifiedUsers;
360 Array::Ptr notifiedProblemUsers = GetNotifiedProblemUsers();
362 for (const User::Ptr& user : allUsers) {
363 String userName = user->GetName();
365 if (!user->GetEnableNotifications()) {
366 Log(LogNotice, "Notification")
367 << "Notification object '" << notificationName << "': Disabled notifications for user '"
368 << userName << "'. Not sending notification.";
372 if (!CheckNotificationUserFilters(type, user, force, reminder)) {
373 Log(LogNotice, "Notification")
374 << "Notification object '" << notificationName << "': Filters for user '" << userName << "' not matched. Not sending notification.";
378 /* on recovery, check if user was notified before */
379 if (type == NotificationRecovery) {
380 if (!notifiedProblemUsers->Contains(userName) && (NotificationProblem & user->GetTypeFilter())) {
381 Log(LogNotice, "Notification")
382 << "Notification object '" << notificationName << "': We did not notify user '" << userName
383 << "' (Problem types enabled) for a problem before. Not sending Recovery notification.";
388 /* on acknowledgement, check if user was notified before */
389 if (type == NotificationAcknowledgement) {
390 if (!notifiedProblemUsers->Contains(userName) && (NotificationProblem & user->GetTypeFilter())) {
391 Log(LogNotice, "Notification")
392 << "Notification object '" << notificationName << "': We did not notify user '" << userName
393 << "' (Problem types enabled) for a problem before. Not sending acknowledgement notification.";
398 Log(LogInformation, "Notification")
399 << "Sending " << (reminder ? "reminder " : "") << "'" << NotificationTypeToString(type) << "' notification '"
400 << notificationName << "' for user '" << userName << "'";
402 Utility::QueueAsyncCallback(std::bind(&Notification::ExecuteNotificationHelper, this, type, user, cr, force, author, text));
404 /* collect all notified users */
405 allNotifiedUsers.insert(user);
407 /* store all notified users for later recovery checks */
408 if (type == NotificationProblem && !notifiedProblemUsers->Contains(userName))
409 notifiedProblemUsers->Add(userName);
412 /* if this was a recovery notification, reset all notified users */
413 if (type == NotificationRecovery)
414 notifiedProblemUsers->Clear();
416 /* used in db_ido for notification history */
417 Service::OnNotificationSentToAllUsers(this, checkable, allNotifiedUsers, type, cr, author, text, nullptr);
420 bool Notification::CheckNotificationUserFilters(NotificationType type, const User::Ptr& user, bool force, bool reminder)
422 String notificationName = GetName();
423 String userName = user->GetName();
426 TimePeriod::Ptr tp = user->GetPeriod();
428 if (tp && !tp->IsInside(Utility::GetTime())) {
429 Log(LogNotice, "Notification")
430 << "Not sending " << (reminder ? "reminder " : "") << "notifications for notification object '"
431 << notificationName << " and user '" << userName
432 << "': user period not in timeperiod '" << tp->GetName() << "'";
436 unsigned long ftype = type;
438 Log(LogDebug, "Notification")
439 << "User '" << userName << "' notification '" << notificationName
440 << "', Type '" << NotificationTypeToString(type)
441 << "', TypeFilter: " << NotificationFilterToString(user->GetTypeFilter(), GetTypeFilterMap())
442 << " (FType=" << ftype << ", TypeFilter=" << GetTypeFilter() << ")";
445 if (!(ftype & user->GetTypeFilter())) {
446 Log(LogNotice, "Notification")
447 << "Not sending " << (reminder ? "reminder " : "") << "notifications for notification object '"
448 << notificationName << " and user '" << userName << "': type '"
449 << NotificationTypeToString(type) << "' does not match type filter: "
450 << NotificationFilterToString(user->GetTypeFilter(), GetTypeFilterMap()) << ".";
454 /* check state filters it this is not a recovery notification */
455 if (type != NotificationRecovery) {
456 Checkable::Ptr checkable = GetCheckable();
458 Service::Ptr service;
459 tie(host, service) = GetHostService(checkable);
461 unsigned long fstate;
465 fstate = ServiceStateToFilter(service->GetState());
466 stateStr = NotificationServiceStateToString(service->GetState());
468 fstate = HostStateToFilter(host->GetState());
469 stateStr = NotificationHostStateToString(host->GetState());
472 Log(LogDebug, "Notification")
473 << "User '" << userName << "' notification '" << notificationName
474 << "', State '" << stateStr << "', StateFilter: "
475 << NotificationFilterToString(user->GetStateFilter(), GetStateFilterMap())
476 << " (FState=" << fstate << ", StateFilter=" << user->GetStateFilter() << ")";
478 if (!(fstate & user->GetStateFilter())) {
479 Log(LogNotice, "Notification")
480 << "Not " << (reminder ? "reminder " : "") << "sending notifications for notification object '"
481 << notificationName << " and user '" << userName << "': state '" << stateStr
482 << "' does not match state filter: " << NotificationFilterToString(user->GetStateFilter(), GetStateFilterMap()) << ".";
487 Log(LogNotice, "Notification")
488 << "Not checking " << (reminder ? "reminder " : "") << "notification filters for notification object '"
489 << notificationName << "' and user '" << userName << "': Notification was forced.";
495 void Notification::ExecuteNotificationHelper(NotificationType type, const User::Ptr& user, const CheckResult::Ptr& cr, bool force, const String& author, const String& text)
497 String notificationName = GetName();
498 String userName = user->GetName();
499 String checkableName = GetCheckable()->GetName();
501 NotificationCommand::Ptr command = GetCommand();
504 Log(LogDebug, "Notification")
505 << "No command found for notification '" << notificationName << "'. Skipping execution.";
509 String commandName = command->GetName();
512 NotificationResult::Ptr nr = new NotificationResult();
514 nr->SetExecutionStart(Utility::GetTime());
516 command->Execute(this, user, cr, nr, type, author, text);
518 /* required by compatlogger */
519 Checkable::OnNotificationSentToUser(this, GetCheckable(), user, type, cr, nr, author, text, command->GetName(), nullptr);
521 Log(LogInformation, "Notification")
522 << "Completed sending '" << NotificationTypeToString(type)
523 << "' notification '" << notificationName
524 << "' for checkable '" << checkableName
525 << "' and user '" << userName << "' using command '" << commandName << "'.";
526 } catch (const std::exception& ex) {
527 Log(LogWarning, "Notification")
528 << "Exception occurred during notification '" << notificationName
529 << "' for checkable '" << checkableName
530 << "' and user '" << userName << "' using command '" << commandName << "': "
531 << DiagnosticInformation(ex, false);
535 void Notification::ProcessNotificationResult(const NotificationResult::Ptr& nr, const MessageOrigin::Ptr& origin)
540 double now = Utility::GetTime();
542 if (nr->GetExecutionStart() == 0)
543 nr->SetExecutionStart(now);
545 if (nr->GetExecutionEnd() == 0)
546 nr->SetExecutionEnd(now);
548 /* Determine the execution endpoint from a locally executed check. */
549 if (!origin || origin->IsLocal())
550 nr->SetExecutionEndpoint(IcingaApplication::GetInstance()->GetNodeName());
556 ObjectLock olock(this);
558 SetLastNotificationResult(nr);
561 /* Notify cluster, API and feature events. */
562 OnNewNotificationResult(this, nr, origin);
565 int icinga::ServiceStateToFilter(ServiceState state)
569 return StateFilterOK;
571 return StateFilterWarning;
572 case ServiceCritical:
573 return StateFilterCritical;
575 return StateFilterUnknown;
577 VERIFY(!"Invalid state type.");
581 int icinga::HostStateToFilter(HostState state)
585 return StateFilterUp;
587 return StateFilterDown;
589 VERIFY(!"Invalid state type.");
593 String Notification::NotificationFilterToString(int filter, const std::map<String, int>& filterMap)
595 std::vector<String> sFilters;
597 typedef std::pair<String, int> kv_pair;
598 for (const kv_pair& kv : filterMap) {
599 if (filter & kv.second)
600 sFilters.push_back(kv.first);
603 return Utility::NaturalJoin(sFilters);
607 * Main interface to translate NotificationType values into strings.
609 String Notification::NotificationTypeToString(NotificationType type)
611 auto typeMap = Notification::m_TypeFilterMap;
613 auto it = std::find_if(typeMap.begin(), typeMap.end(),
614 [&type](const std::pair<String, int>& p) {
615 return p.second == type;
618 if (it == typeMap.end())
626 * Compat interface used in external features.
628 String Notification::NotificationTypeToStringCompat(NotificationType type)
631 case NotificationDowntimeStart:
632 return "DOWNTIMESTART";
633 case NotificationDowntimeEnd:
634 return "DOWNTIMEEND";
635 case NotificationDowntimeRemoved:
636 return "DOWNTIMECANCELLED";
637 case NotificationCustom:
639 case NotificationAcknowledgement:
640 return "ACKNOWLEDGEMENT";
641 case NotificationProblem:
643 case NotificationRecovery:
645 case NotificationFlappingStart:
646 return "FLAPPINGSTART";
647 case NotificationFlappingEnd:
648 return "FLAPPINGEND";
650 return "UNKNOWN_NOTIFICATION";
654 String Notification::NotificationServiceStateToString(ServiceState state)
661 case ServiceCritical:
666 VERIFY(!"Invalid state type.");
670 String Notification::NotificationHostStateToString(HostState state)
678 VERIFY(!"Invalid state type.");
682 void Notification::Validate(int types, const ValidationUtils& utils)
684 ObjectImpl<Notification>::Validate(types, utils);
686 if (!(types & FAConfig))
689 Array::Ptr users = GetUsersRaw();
690 Array::Ptr groups = GetUserGroupsRaw();
692 if ((!users || users->GetLength() == 0) && (!groups || groups->GetLength() == 0))
693 BOOST_THROW_EXCEPTION(ValidationError(this, std::vector<String>(), "Validation failed: No users/user_groups specified."));
696 void Notification::ValidateStates(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils)
698 ObjectImpl<Notification>::ValidateStates(lvalue, utils);
700 int filter = FilterArrayToInt(lvalue(), GetStateFilterMap(), 0);
702 if (GetServiceName().IsEmpty() && (filter == -1 || (filter & ~(StateFilterUp | StateFilterDown)) != 0))
703 BOOST_THROW_EXCEPTION(ValidationError(this, { "states" }, "State filter is invalid."));
705 if (!GetServiceName().IsEmpty() && (filter == -1 || (filter & ~(StateFilterOK | StateFilterWarning | StateFilterCritical | StateFilterUnknown)) != 0))
706 BOOST_THROW_EXCEPTION(ValidationError(this, { "states" }, "State filter is invalid."));
709 void Notification::ValidateTypes(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils)
711 ObjectImpl<Notification>::ValidateTypes(lvalue, utils);
713 int filter = FilterArrayToInt(lvalue(), GetTypeFilterMap(), 0);
715 if (filter == -1 || (filter & ~(NotificationDowntimeStart | NotificationDowntimeEnd | NotificationDowntimeRemoved |
716 NotificationCustom | NotificationAcknowledgement | NotificationProblem | NotificationRecovery |
717 NotificationFlappingStart | NotificationFlappingEnd)) != 0)
718 BOOST_THROW_EXCEPTION(ValidationError(this, { "types" }, "Type filter is invalid."));
721 void Notification::ValidateTimes(const Lazy<Dictionary::Ptr>& lvalue, const ValidationUtils& utils)
723 ObjectImpl<Notification>::ValidateTimes(lvalue, utils);
725 Dictionary::Ptr times = lvalue();
734 begin = Convert::ToDouble(times->Get("begin"));
735 } catch (const std::exception&) {
736 BOOST_THROW_EXCEPTION(ValidationError(this, { "times" }, "'begin' is invalid, must be duration or number." ));
740 end = Convert::ToDouble(times->Get("end"));
741 } catch (const std::exception&) {
742 BOOST_THROW_EXCEPTION(ValidationError(this, { "times" }, "'end' is invalid, must be duration or number." ));
745 /* Also solve logical errors where begin > end. */
746 if (begin > 0 && end > 0 && begin > end)
747 BOOST_THROW_EXCEPTION(ValidationError(this, { "times" }, "'begin' must be smaller than 'end'."));
750 Endpoint::Ptr Notification::GetCommandEndpoint() const
752 return Endpoint::GetByName(GetCommandEndpointRaw());
755 const std::map<String, int>& Notification::GetStateFilterMap()
757 return m_StateFilterMap;
760 const std::map<String, int>& Notification::GetTypeFilterMap()
762 return m_TypeFilterMap;