1 /******************************************************************************
3 * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org) *
5 * This program is free software; you can redistribute it and/or *
6 * modify it under the terms of the GNU General Public License *
7 * as published by the Free Software Foundation; either version 2 *
8 * of the License, or (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the Free Software Foundation *
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. *
18 ******************************************************************************/
20 #include "icinga/notification.hpp"
21 #include "icinga/notificationcommand.hpp"
22 #include "icinga/service.hpp"
23 #include "base/objectlock.hpp"
24 #include "base/logger.hpp"
25 #include "base/utility.hpp"
26 #include "base/convert.hpp"
27 #include "base/exception.hpp"
28 #include "base/initialize.hpp"
29 #include "base/scriptglobal.hpp"
30 #include "base/scriptfunction.hpp"
31 #include <boost/foreach.hpp>
33 using namespace icinga;
35 REGISTER_TYPE(Notification);
36 REGISTER_SCRIPTFUNCTION(ValidateNotificationFilters, &Notification::ValidateFilters);
37 INITIALIZE_ONCE(&Notification::StaticInitialize);
39 boost::signals2::signal<void (const Notification::Ptr&, double, const MessageOrigin&)> Notification::OnNextNotificationChanged;
41 String NotificationNameComposer::MakeName(const String& shortName, const Object::Ptr& context) const
43 Notification::Ptr notification = dynamic_pointer_cast<Notification>(context);
48 String name = notification->GetHostName();
50 if (!notification->GetServiceName().IsEmpty())
51 name += "!" + notification->GetServiceName();
53 name += "!" + shortName;
58 void Notification::StaticInitialize(void)
60 ScriptGlobal::Set("OK", StateFilterOK);
61 ScriptGlobal::Set("Warning", StateFilterWarning);
62 ScriptGlobal::Set("Critical", StateFilterCritical);
63 ScriptGlobal::Set("Unknown", StateFilterUnknown);
64 ScriptGlobal::Set("Up", StateFilterUp);
65 ScriptGlobal::Set("Down", StateFilterDown);
67 ScriptGlobal::Set("DowntimeStart", 1 << NotificationDowntimeStart);
68 ScriptGlobal::Set("DowntimeEnd", 1 << NotificationDowntimeEnd);
69 ScriptGlobal::Set("DowntimeRemoved", 1 << NotificationDowntimeRemoved);
70 ScriptGlobal::Set("Custom", 1 << NotificationCustom);
71 ScriptGlobal::Set("Acknowledgement", 1 << NotificationAcknowledgement);
72 ScriptGlobal::Set("Problem", 1 << NotificationProblem);
73 ScriptGlobal::Set("Recovery", 1 << NotificationRecovery);
74 ScriptGlobal::Set("FlappingStart", 1 << NotificationFlappingStart);
75 ScriptGlobal::Set("FlappingEnd", 1 << NotificationFlappingEnd);
78 void Notification::OnConfigLoaded(void)
80 SetTypeFilter(FilterArrayToInt(GetTypes(), ~0));
81 SetStateFilter(FilterArrayToInt(GetStates(), ~0));
84 void Notification::OnAllConfigLoaded(void)
86 Checkable::Ptr obj = GetCheckable();
89 BOOST_THROW_EXCEPTION(ScriptError("Notification object refers to a host/service which doesn't exist.", GetDebugInfo()));
91 obj->AddNotification(this);
94 void Notification::Start(void)
96 DynamicObject::Start();
98 Checkable::Ptr obj = GetCheckable();
101 obj->AddNotification(this);
104 void Notification::Stop(void)
106 DynamicObject::Stop();
108 Checkable::Ptr obj = GetCheckable();
111 obj->RemoveNotification(this);
114 Checkable::Ptr Notification::GetCheckable(void) const
116 Host::Ptr host = Host::GetByName(GetHostName());
118 if (GetServiceName().IsEmpty())
121 return host->GetServiceByShortName(GetServiceName());
124 NotificationCommand::Ptr Notification::GetCommand(void) const
126 return NotificationCommand::GetByName(GetCommandRaw());
129 std::set<User::Ptr> Notification::GetUsers(void) const
131 std::set<User::Ptr> result;
133 Array::Ptr users = GetUsersRaw();
136 ObjectLock olock(users);
138 BOOST_FOREACH(const String& name, users) {
139 User::Ptr user = User::GetByName(name);
151 std::set<UserGroup::Ptr> Notification::GetUserGroups(void) const
153 std::set<UserGroup::Ptr> result;
155 Array::Ptr groups = GetUserGroupsRaw();
158 ObjectLock olock(groups);
160 BOOST_FOREACH(const String& name, groups) {
161 UserGroup::Ptr ug = UserGroup::GetByName(name);
173 TimePeriod::Ptr Notification::GetPeriod(void) const
175 return TimePeriod::GetByName(GetPeriodRaw());
178 double Notification::GetNextNotification(void) const
180 return GetNextNotificationRaw();
184 * Sets the timestamp when the next periodical notification should be sent.
185 * This does not affect notifications that are sent for state changes.
187 void Notification::SetNextNotification(double time, const MessageOrigin& origin)
189 SetNextNotificationRaw(time);
191 OnNextNotificationChanged(this, time, origin);
194 void Notification::UpdateNotificationNumber(void)
196 SetNotificationNumber(GetNotificationNumber() + 1);
199 void Notification::ResetNotificationNumber(void)
201 SetNotificationNumber(0);
204 String Notification::NotificationTypeToString(NotificationType type)
207 case NotificationDowntimeStart:
208 return "DOWNTIMESTART";
209 case NotificationDowntimeEnd:
210 return "DOWNTIMEEND";
211 case NotificationDowntimeRemoved:
212 return "DOWNTIMECANCELLED";
213 case NotificationCustom:
215 case NotificationAcknowledgement:
216 return "ACKNOWLEDGEMENT";
217 case NotificationProblem:
219 case NotificationRecovery:
221 case NotificationFlappingStart:
222 return "FLAPPINGSTART";
223 case NotificationFlappingEnd:
224 return "FLAPPINGEND";
226 return "UNKNOWN_NOTIFICATION";
230 void Notification::BeginExecuteNotification(NotificationType type, const CheckResult::Ptr& cr, bool force, const String& author, const String& text)
234 Checkable::Ptr checkable = GetCheckable();
237 TimePeriod::Ptr tp = GetPeriod();
239 if (tp && !tp->IsInside(Utility::GetTime())) {
240 Log(LogNotice, "Notification")
241 << "Not sending notifications for notification object '" << GetName() << "': not in timeperiod";
245 double now = Utility::GetTime();
246 Dictionary::Ptr times = GetTimes();
248 if (type == NotificationProblem) {
249 if (times && times->Contains("begin") && now < checkable->GetLastHardStateChange() + times->Get("begin")) {
250 Log(LogNotice, "Notification")
251 << "Not sending notifications for notification object '" << GetName() << "': before escalation range";
253 /* we need to adjust the next notification time
254 * to now + begin delaying the first notification
256 double nextProposedNotification = now + times->Get("begin") + 1.0;
257 if (GetNextNotification() > nextProposedNotification)
258 SetNextNotification(nextProposedNotification);
263 if (times && times->Contains("end") && now > checkable->GetLastHardStateChange() + times->Get("end")) {
264 Log(LogNotice, "Notification")
265 << "Not sending notifications for notification object '" << GetName() << "': after escalation range";
270 unsigned long ftype = 1 << type;
272 Log(LogDebug, "Notification")
273 << "FType=" << ftype << ", TypeFilter=" << GetTypeFilter();
275 if (!(ftype & GetTypeFilter())) {
276 Log(LogNotice, "Notification")
277 << "Not sending notifications for notification object '" << GetName() << "': type filter does not match '"
278 << NotificationTypeToString(type) << "'";
282 /* ensure that recovery notifications are always sent, no state filter checks necessary */
283 if (type != NotificationRecovery) {
285 Service::Ptr service;
286 tie(host, service) = GetHostService(checkable);
288 unsigned long fstate;
291 fstate = ServiceStateToFilter(service->GetState());
293 fstate = HostStateToFilter(host->GetState());
295 if (!(fstate & GetStateFilter())) {
296 Log(LogNotice, "Notification")
297 << "Not sending notifications for notification object '" << GetName() << "': state filter does not match";
304 ObjectLock olock(this);
306 double now = Utility::GetTime();
307 SetLastNotification(now);
309 if (type == NotificationProblem)
310 SetLastProblemNotification(now);
313 std::set<User::Ptr> allUsers;
315 std::set<User::Ptr> users = GetUsers();
316 std::copy(users.begin(), users.end(), std::inserter(allUsers, allUsers.begin()));
318 BOOST_FOREACH(const UserGroup::Ptr& ug, GetUserGroups()) {
319 std::set<User::Ptr> members = ug->GetMembers();
320 std::copy(members.begin(), members.end(), std::inserter(allUsers, allUsers.begin()));
323 Service::OnNotificationSendStart(this, checkable, allUsers, type, cr, author, text);
325 std::set<User::Ptr> allNotifiedUsers;
326 Array::Ptr notifiedUsers = GetNotifiedUsers();
328 BOOST_FOREACH(const User::Ptr& user, allUsers) {
329 String userName = user->GetName();
331 if (!user->GetEnableNotifications()) {
332 Log(LogNotice, "Notification")
333 << "Disabled notifications for user '" << userName << "'. Not sending notification.";
337 if (!CheckNotificationUserFilters(type, user, force)) {
338 Log(LogNotice, "Notification")
339 << "Notification filters for user '" << userName << "' not matched. Not sending notification.";
343 /* on recovery, check if user was notified before */
344 if (type == NotificationRecovery) {
345 if (!notifiedUsers->Contains(userName)) {
346 Log(LogNotice, "Notification")
347 << "We did not notify user '" << userName << "' before. Not sending recovery notification.";
352 Log(LogInformation, "Notification")
353 << "Sending notification for user '" << userName << "'";
355 Utility::QueueAsyncCallback(boost::bind(&Notification::ExecuteNotificationHelper, this, type, user, cr, force, author, text));
357 /* collect all notified users */
358 allNotifiedUsers.insert(user);
360 /* store all notified users for later recovery checks */
361 if (!notifiedUsers->Contains(userName))
362 notifiedUsers->Add(userName);
365 /* if this was a recovery notification, reset all notified users */
366 if (type == NotificationRecovery)
367 notifiedUsers->Clear();
369 /* used in db_ido for notification history */
370 Service::OnNotificationSentToAllUsers(this, checkable, allNotifiedUsers, type, cr, author, text);
373 bool Notification::CheckNotificationUserFilters(NotificationType type, const User::Ptr& user, bool force)
378 TimePeriod::Ptr tp = user->GetPeriod();
380 if (tp && !tp->IsInside(Utility::GetTime())) {
381 Log(LogNotice, "Notification")
382 << "Not sending notifications for notification object '"
383 << GetName() << " and user '" << user->GetName() << "': user not in timeperiod";
387 unsigned long ftype = 1 << type;
389 if (!(ftype & user->GetTypeFilter())) {
390 Log(LogNotice, "Notification")
391 << "Not sending notifications for notification object '"
392 << GetName() << " and user '" << user->GetName() << "': type filter does not match";
396 /* check state filters it this is not a recovery notification */
397 if (type != NotificationRecovery) {
398 Checkable::Ptr checkable = GetCheckable();
400 Service::Ptr service;
401 tie(host, service) = GetHostService(checkable);
403 unsigned long fstate;
406 fstate = ServiceStateToFilter(service->GetState());
408 fstate = HostStateToFilter(host->GetState());
410 if (!(fstate & user->GetStateFilter())) {
411 Log(LogNotice, "Notification")
412 << "Not sending notifications for notification object '"
413 << GetName() << " and user '" << user->GetName() << "': state filter does not match";
422 void Notification::ExecuteNotificationHelper(NotificationType type, const User::Ptr& user, const CheckResult::Ptr& cr, bool force, const String& author, const String& text)
427 NotificationCommand::Ptr command = GetCommand();
430 Log(LogDebug, "Notification")
431 << "No notification_command found for notification '" << GetName() << "'. Skipping execution.";
435 command->Execute(this, user, cr, type, author, text);
438 ObjectLock olock(this);
439 UpdateNotificationNumber();
440 SetLastNotification(Utility::GetTime());
443 /* required by compatlogger */
444 Service::OnNotificationSentToUser(this, GetCheckable(), user, type, cr, author, text, command->GetName());
446 Log(LogInformation, "Notification")
447 << "Completed sending notification for object '" << GetCheckable()->GetName() << "'";
448 } catch (const std::exception& ex) {
449 Log(LogWarning, "Notification")
450 << "Exception occured during notification for object '"
451 << GetCheckable()->GetName() << "': " << DiagnosticInformation(ex);
455 int icinga::ServiceStateToFilter(ServiceState state)
459 return StateFilterOK;
461 return StateFilterWarning;
462 case ServiceCritical:
463 return StateFilterCritical;
465 return StateFilterUnknown;
467 VERIFY(!"Invalid state type.");
471 int icinga::HostStateToFilter(HostState state)
475 return StateFilterUp;
477 return StateFilterDown;
479 VERIFY(!"Invalid state type.");
483 int icinga::FilterArrayToInt(const Array::Ptr& typeFilters, int defaultValue)
485 Value resultTypeFilter;
490 resultTypeFilter = 0;
492 ObjectLock olock(typeFilters);
493 BOOST_FOREACH(const Value& typeFilter, typeFilters) {
494 resultTypeFilter = resultTypeFilter | typeFilter;
497 return resultTypeFilter;
500 void Notification::ValidateFilters(const String& location, const Notification::Ptr& object)
502 int sfilter = FilterArrayToInt(object->GetStates(), 0);
504 if (object->GetServiceName().IsEmpty() && (sfilter & ~(StateFilterUp | StateFilterDown)) != 0) {
505 BOOST_THROW_EXCEPTION(ScriptError("Validation failed for " +
506 location + ": State filter is invalid.", object->GetDebugInfo()));
509 if (!object->GetServiceName().IsEmpty() && (sfilter & ~(StateFilterOK | StateFilterWarning | StateFilterCritical | StateFilterUnknown)) != 0) {
510 BOOST_THROW_EXCEPTION(ScriptError("Validation failed for " +
511 location + ": State filter is invalid.", object->GetDebugInfo()));
514 int tfilter = FilterArrayToInt(object->GetTypes(), 0);
516 if ((tfilter & ~(1 << NotificationDowntimeStart | 1 << NotificationDowntimeEnd | 1 << NotificationDowntimeRemoved |
517 1 << NotificationCustom | 1 << NotificationAcknowledgement | 1 << NotificationProblem | 1 << NotificationRecovery |
518 1 << NotificationFlappingStart | 1 << NotificationFlappingEnd)) != 0) {
519 BOOST_THROW_EXCEPTION(ScriptError("Validation failed for " +
520 location + ": Type filter is invalid.", object->GetDebugInfo()));
524 Endpoint::Ptr Notification::GetCommandEndpoint(void) const
526 return Endpoint::GetByName(GetCommandEndpointRaw());