]> granicus.if.org Git - icinga2/blob - lib/icinga/notification.cpp
799b60ae517dd7eb52f94a79f1f07bc5dd25028f
[icinga2] / lib / icinga / notification.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2014 Icinga Development Team (http://www.icinga.org)    *
4  *                                                                            *
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.                     *
9  *                                                                            *
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.                               *
14  *                                                                            *
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  ******************************************************************************/
19
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>
32
33 using namespace icinga;
34
35 REGISTER_TYPE(Notification);
36 REGISTER_SCRIPTFUNCTION(ValidateNotificationFilters, &Notification::ValidateFilters);
37 INITIALIZE_ONCE(&Notification::StaticInitialize);
38
39 boost::signals2::signal<void (const Notification::Ptr&, double, const MessageOrigin&)> Notification::OnNextNotificationChanged;
40
41 String NotificationNameComposer::MakeName(const String& shortName, const Object::Ptr& context) const
42 {
43         Notification::Ptr notification = dynamic_pointer_cast<Notification>(context);
44
45         if (!notification)
46                 return "";
47
48         String name = notification->GetHostName();
49
50         if (!notification->GetServiceName().IsEmpty())
51                 name += "!" + notification->GetServiceName();
52
53         name += "!" + shortName;
54
55         return name;
56 }
57
58 void Notification::StaticInitialize(void)
59 {
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);
66
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);
76 }
77
78 void Notification::OnConfigLoaded(void)
79 {
80         SetTypeFilter(FilterArrayToInt(GetTypes(), ~0));
81         SetStateFilter(FilterArrayToInt(GetStates(), ~0));
82 }
83
84 void Notification::OnAllConfigLoaded(void)
85 {
86         Checkable::Ptr obj = GetCheckable();
87
88         if (!obj)
89                 BOOST_THROW_EXCEPTION(ScriptError("Notification object refers to a host/service which doesn't exist.", GetDebugInfo()));
90
91         obj->AddNotification(this);
92 }
93
94 void Notification::Start(void)
95 {
96         DynamicObject::Start();
97
98         Checkable::Ptr obj = GetCheckable();
99
100         if (obj)
101                 obj->AddNotification(this);
102 }
103
104 void Notification::Stop(void)
105 {
106         DynamicObject::Stop();
107
108         Checkable::Ptr obj = GetCheckable();
109
110         if (obj)
111                 obj->RemoveNotification(this);
112 }
113
114 Checkable::Ptr Notification::GetCheckable(void) const
115 {
116         Host::Ptr host = Host::GetByName(GetHostName());
117
118         if (GetServiceName().IsEmpty())
119                 return host;
120         else
121                 return host->GetServiceByShortName(GetServiceName());
122 }
123
124 NotificationCommand::Ptr Notification::GetCommand(void) const
125 {
126         return NotificationCommand::GetByName(GetCommandRaw());
127 }
128
129 std::set<User::Ptr> Notification::GetUsers(void) const
130 {
131         std::set<User::Ptr> result;
132
133         Array::Ptr users = GetUsersRaw();
134
135         if (users) {
136                 ObjectLock olock(users);
137
138                 BOOST_FOREACH(const String& name, users) {
139                         User::Ptr user = User::GetByName(name);
140
141                         if (!user)
142                                 continue;
143
144                         result.insert(user);
145                 }
146         }
147
148         return result;
149 }
150
151 std::set<UserGroup::Ptr> Notification::GetUserGroups(void) const
152 {
153         std::set<UserGroup::Ptr> result;
154
155         Array::Ptr groups = GetUserGroupsRaw();
156
157         if (groups) {
158                 ObjectLock olock(groups);
159
160                 BOOST_FOREACH(const String& name, groups) {
161                         UserGroup::Ptr ug = UserGroup::GetByName(name);
162
163                         if (!ug)
164                                 continue;
165
166                         result.insert(ug);
167                 }
168         }
169
170         return result;
171 }
172
173 TimePeriod::Ptr Notification::GetPeriod(void) const
174 {
175         return TimePeriod::GetByName(GetPeriodRaw());
176 }
177
178 double Notification::GetNextNotification(void) const
179 {
180         return GetNextNotificationRaw();
181 }
182
183 /**
184  * Sets the timestamp when the next periodical notification should be sent.
185  * This does not affect notifications that are sent for state changes.
186  */
187 void Notification::SetNextNotification(double time, const MessageOrigin& origin)
188 {
189         SetNextNotificationRaw(time);
190
191         OnNextNotificationChanged(this, time, origin);
192 }
193
194 void Notification::UpdateNotificationNumber(void)
195 {
196         SetNotificationNumber(GetNotificationNumber() + 1);
197 }
198
199 void Notification::ResetNotificationNumber(void)
200 {
201         SetNotificationNumber(0);
202 }
203
204 String Notification::NotificationTypeToString(NotificationType type)
205 {
206         switch (type) {
207                 case NotificationDowntimeStart:
208                         return "DOWNTIMESTART";
209                 case NotificationDowntimeEnd:
210                         return "DOWNTIMEEND";
211                 case NotificationDowntimeRemoved:
212                         return "DOWNTIMECANCELLED";
213                 case NotificationCustom:
214                         return "CUSTOM";
215                 case NotificationAcknowledgement:
216                         return "ACKNOWLEDGEMENT";
217                 case NotificationProblem:
218                         return "PROBLEM";
219                 case NotificationRecovery:
220                         return "RECOVERY";
221                 case NotificationFlappingStart:
222                         return "FLAPPINGSTART";
223                 case NotificationFlappingEnd:
224                         return "FLAPPINGEND";
225                 default:
226                         return "UNKNOWN_NOTIFICATION";
227         }
228 }
229
230 void Notification::BeginExecuteNotification(NotificationType type, const CheckResult::Ptr& cr, bool force, const String& author, const String& text)
231 {
232         ASSERT(!OwnsLock());
233
234         Checkable::Ptr checkable = GetCheckable();
235
236         if (!force) {
237                 TimePeriod::Ptr tp = GetPeriod();
238
239                 if (tp && !tp->IsInside(Utility::GetTime())) {
240                         Log(LogNotice, "Notification")
241                             << "Not sending notifications for notification object '" << GetName() << "': not in timeperiod";
242                         return;
243                 }
244
245                 double now = Utility::GetTime();
246                 Dictionary::Ptr times = GetTimes();
247
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";
252
253                                 /* we need to adjust the next notification time
254                                  * to now + begin delaying the first notification
255                                  */
256                                 double nextProposedNotification = now + times->Get("begin") + 1.0;
257                                 if (GetNextNotification() > nextProposedNotification)
258                                         SetNextNotification(nextProposedNotification);
259
260                                 return;
261                         }
262
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";
266                                 return;
267                         }
268                 }
269
270                 unsigned long ftype = 1 << type;
271
272                 Log(LogDebug, "Notification")
273                     << "FType=" << ftype << ", TypeFilter=" << GetTypeFilter();
274
275                 if (!(ftype & GetTypeFilter())) {
276                         Log(LogNotice, "Notification")
277                             << "Not sending notifications for notification object '" << GetName() << "': type filter does not match '"
278                             << NotificationTypeToString(type) << "'";
279                         return;
280                 }
281
282                 /* ensure that recovery notifications are always sent, no state filter checks necessary */
283                 if (type != NotificationRecovery) {
284                         Host::Ptr host;
285                         Service::Ptr service;
286                         tie(host, service) = GetHostService(checkable);
287
288                         unsigned long fstate;
289
290                         if (service)
291                                 fstate = ServiceStateToFilter(service->GetState());
292                         else
293                                 fstate = HostStateToFilter(host->GetState());
294
295                         if (!(fstate & GetStateFilter())) {
296                                 Log(LogNotice, "Notification")
297                                     << "Not sending notifications for notification object '" << GetName() << "': state filter does not match";
298                                 return;
299                         }
300                 }
301         }
302
303         {
304                 ObjectLock olock(this);
305
306                 double now = Utility::GetTime();
307                 SetLastNotification(now);
308
309                 if (type == NotificationProblem)
310                         SetLastProblemNotification(now);
311         }
312
313         std::set<User::Ptr> allUsers;
314
315         std::set<User::Ptr> users = GetUsers();
316         std::copy(users.begin(), users.end(), std::inserter(allUsers, allUsers.begin()));
317
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()));
321         }
322
323         Service::OnNotificationSendStart(this, checkable, allUsers, type, cr, author, text);
324
325         std::set<User::Ptr> allNotifiedUsers;
326         Array::Ptr notifiedUsers = GetNotifiedUsers();
327
328         BOOST_FOREACH(const User::Ptr& user, allUsers) {
329                 String userName = user->GetName();
330
331                 if (!user->GetEnableNotifications()) {
332                         Log(LogNotice, "Notification")
333                             << "Disabled notifications for user '" << userName << "'. Not sending notification.";
334                         continue;
335                 }
336
337                 if (!CheckNotificationUserFilters(type, user, force)) {
338                         Log(LogNotice, "Notification")
339                             << "Notification filters for user '" << userName << "' not matched. Not sending notification.";
340                         continue;
341                 }
342
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.";
348                                 continue;
349                         }
350                 }
351
352                 Log(LogInformation, "Notification")
353                     << "Sending notification for user '" << userName << "'";
354
355                 Utility::QueueAsyncCallback(boost::bind(&Notification::ExecuteNotificationHelper, this, type, user, cr, force, author, text));
356
357                 /* collect all notified users */
358                 allNotifiedUsers.insert(user);
359
360                 /* store all notified users for later recovery checks */
361                 if (!notifiedUsers->Contains(userName))
362                         notifiedUsers->Add(userName);
363         }
364
365         /* if this was a recovery notification, reset all notified users */
366         if (type == NotificationRecovery)
367                 notifiedUsers->Clear();
368
369         /* used in db_ido for notification history */
370         Service::OnNotificationSentToAllUsers(this, checkable, allNotifiedUsers, type, cr, author, text);
371 }
372
373 bool Notification::CheckNotificationUserFilters(NotificationType type, const User::Ptr& user, bool force)
374 {
375         ASSERT(!OwnsLock());
376
377         if (!force) {
378                 TimePeriod::Ptr tp = user->GetPeriod();
379
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";
384                         return false;
385                 }
386
387                 unsigned long ftype = 1 << type;
388
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";
393                         return false;
394                 }
395
396                 /* check state filters it this is not a recovery notification */
397                 if (type != NotificationRecovery) {
398                         Checkable::Ptr checkable = GetCheckable();
399                         Host::Ptr host;
400                         Service::Ptr service;
401                         tie(host, service) = GetHostService(checkable);
402
403                         unsigned long fstate;
404
405                         if (service)
406                                         fstate = ServiceStateToFilter(service->GetState());
407                         else
408                                         fstate = HostStateToFilter(host->GetState());
409
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";
414                                 return false;
415                         }
416                 }
417         }
418
419         return true;
420 }
421
422 void Notification::ExecuteNotificationHelper(NotificationType type, const User::Ptr& user, const CheckResult::Ptr& cr, bool force, const String& author, const String& text)
423 {
424         ASSERT(!OwnsLock());
425
426         try {
427                 NotificationCommand::Ptr command = GetCommand();
428
429                 if (!command) {
430                         Log(LogDebug, "Notification")
431                             << "No notification_command found for notification '" << GetName() << "'. Skipping execution.";
432                         return;
433                 }
434
435                 command->Execute(this, user, cr, type, author, text);
436
437                 {
438                         ObjectLock olock(this);
439                         UpdateNotificationNumber();
440                         SetLastNotification(Utility::GetTime());
441                 }
442
443                 /* required by compatlogger */
444                 Service::OnNotificationSentToUser(this, GetCheckable(), user, type, cr, author, text, command->GetName());
445
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);
452         }
453 }
454
455 int icinga::ServiceStateToFilter(ServiceState state)
456 {
457         switch (state) {
458                 case ServiceOK:
459                         return StateFilterOK;
460                 case ServiceWarning:
461                         return StateFilterWarning;
462                 case ServiceCritical:
463                         return StateFilterCritical;
464                 case ServiceUnknown:
465                         return StateFilterUnknown;
466                 default:
467                         VERIFY(!"Invalid state type.");
468         }
469 }
470
471 int icinga::HostStateToFilter(HostState state)
472 {
473         switch (state) {
474                 case HostUp:
475                         return StateFilterUp;
476                 case HostDown:
477                         return StateFilterDown;
478                 default:
479                         VERIFY(!"Invalid state type.");
480         }
481 }
482
483 int icinga::FilterArrayToInt(const Array::Ptr& typeFilters, int defaultValue)
484 {
485         Value resultTypeFilter;
486
487         if (!typeFilters)
488                 return defaultValue;
489
490         resultTypeFilter = 0;
491
492         ObjectLock olock(typeFilters);
493         BOOST_FOREACH(const Value& typeFilter, typeFilters) {
494                 resultTypeFilter = resultTypeFilter | typeFilter;
495         }
496
497         return resultTypeFilter;
498 }
499
500 void Notification::ValidateFilters(const String& location, const Notification::Ptr& object)
501 {
502         int sfilter = FilterArrayToInt(object->GetStates(), 0);
503
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()));
507         }
508
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()));
512         }
513
514         int tfilter = FilterArrayToInt(object->GetTypes(), 0);
515
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()));
521         }
522 }
523
524 Endpoint::Ptr Notification::GetCommandEndpoint(void) const
525 {
526         return Endpoint::GetByName(GetCommandEndpointRaw());
527 }
528