]> granicus.if.org Git - icinga2/blob - lib/icinga/notification.cpp
350a3deae9accde288ba650462172118f5f64921
[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 "config/configcompilercontext.hpp"
24 #include "base/objectlock.hpp"
25 #include "base/logger.hpp"
26 #include "base/utility.hpp"
27 #include "base/convert.hpp"
28 #include "base/exception.hpp"
29 #include "base/initialize.hpp"
30 #include "base/scriptvariable.hpp"
31 #include "base/scriptfunction.hpp"
32 #include <boost/foreach.hpp>
33
34 using namespace icinga;
35
36 REGISTER_TYPE(Notification);
37 REGISTER_SCRIPTFUNCTION(ValidateNotificationFilters, &Notification::ValidateFilters);
38 INITIALIZE_ONCE(&Notification::StaticInitialize);
39
40 boost::signals2::signal<void (const Notification::Ptr&, double, const MessageOrigin&)> Notification::OnNextNotificationChanged;
41
42 String NotificationNameComposer::MakeName(const String& shortName, const Object::Ptr& context) const
43 {
44         Notification::Ptr notification = dynamic_pointer_cast<Notification>(context);
45
46         if (!notification)
47                 return "";
48
49         String name = notification->GetHostName();
50
51         if (!notification->GetServiceName().IsEmpty())
52                 name += "!" + notification->GetServiceName();
53
54         name += "!" + shortName;
55
56         return name;
57 }
58
59 void Notification::StaticInitialize(void)
60 {
61         ScriptVariable::Set("OK", StateFilterOK, true, true);
62         ScriptVariable::Set("Warning", StateFilterWarning, true, true);
63         ScriptVariable::Set("Critical", StateFilterCritical, true, true);
64         ScriptVariable::Set("Unknown", StateFilterUnknown, true, true);
65         ScriptVariable::Set("Up", StateFilterUp, true, true);
66         ScriptVariable::Set("Down", StateFilterDown, true, true);
67
68         ScriptVariable::Set("DowntimeStart", 1 << NotificationDowntimeStart, true, true);
69         ScriptVariable::Set("DowntimeEnd", 1 << NotificationDowntimeEnd, true, true);
70         ScriptVariable::Set("DowntimeRemoved", 1 << NotificationDowntimeRemoved, true, true);
71         ScriptVariable::Set("Custom", 1 << NotificationCustom, true, true);
72         ScriptVariable::Set("Acknowledgement", 1 << NotificationAcknowledgement, true, true);
73         ScriptVariable::Set("Problem", 1 << NotificationProblem, true, true);
74         ScriptVariable::Set("Recovery", 1 << NotificationRecovery, true, true);
75         ScriptVariable::Set("FlappingStart", 1 << NotificationFlappingStart, true, true);
76         ScriptVariable::Set("FlappingEnd", 1 << NotificationFlappingEnd, true, true);
77 }
78
79 void Notification::OnConfigLoaded(void)
80 {
81         SetTypeFilter(FilterArrayToInt(GetTypes(), ~0));
82         SetStateFilter(FilterArrayToInt(GetStates(), ~0));
83
84         Checkable::Ptr obj = GetCheckable();
85
86         if (obj)
87                 obj->AddNotification(this);
88 }
89
90 void Notification::Start(void)
91 {
92         DynamicObject::Start();
93
94         Checkable::Ptr obj = GetCheckable();
95
96         if (obj)
97                 obj->AddNotification(this);
98 }
99
100 void Notification::Stop(void)
101 {
102         DynamicObject::Stop();
103
104         Checkable::Ptr obj = GetCheckable();
105
106         if (obj)
107                 obj->RemoveNotification(this);
108 }
109
110 Checkable::Ptr Notification::GetCheckable(void) const
111 {
112         Host::Ptr host = Host::GetByName(GetHostName());
113
114         if (GetServiceName().IsEmpty())
115                 return host;
116         else
117                 return host->GetServiceByShortName(GetServiceName());
118 }
119
120 NotificationCommand::Ptr Notification::GetCommand(void) const
121 {
122         return NotificationCommand::GetByName(GetCommandRaw());
123 }
124
125 std::set<User::Ptr> Notification::GetUsers(void) const
126 {
127         std::set<User::Ptr> result;
128
129         Array::Ptr users = GetUsersRaw();
130
131         if (users) {
132                 ObjectLock olock(users);
133
134                 BOOST_FOREACH(const String& name, users) {
135                         User::Ptr user = User::GetByName(name);
136
137                         if (!user)
138                                 continue;
139
140                         result.insert(user);
141                 }
142         }
143
144         return result;
145 }
146
147 std::set<UserGroup::Ptr> Notification::GetUserGroups(void) const
148 {
149         std::set<UserGroup::Ptr> result;
150
151         Array::Ptr groups = GetUserGroupsRaw();
152
153         if (groups) {
154                 ObjectLock olock(groups);
155
156                 BOOST_FOREACH(const String& name, groups) {
157                         UserGroup::Ptr ug = UserGroup::GetByName(name);
158
159                         if (!ug)
160                                 continue;
161
162                         result.insert(ug);
163                 }
164         }
165
166         return result;
167 }
168
169 TimePeriod::Ptr Notification::GetPeriod(void) const
170 {
171         return TimePeriod::GetByName(GetPeriodRaw());
172 }
173
174 double Notification::GetNextNotification(void) const
175 {
176         return GetNextNotificationRaw();
177 }
178
179 /**
180  * Sets the timestamp when the next periodical notification should be sent.
181  * This does not affect notifications that are sent for state changes.
182  */
183 void Notification::SetNextNotification(double time, const MessageOrigin& origin)
184 {
185         SetNextNotificationRaw(time);
186
187         OnNextNotificationChanged(this, time, origin);
188 }
189
190 void Notification::UpdateNotificationNumber(void)
191 {
192         SetNotificationNumber(GetNotificationNumber() + 1);
193 }
194
195 void Notification::ResetNotificationNumber(void)
196 {
197         SetNotificationNumber(0);
198 }
199
200 String Notification::NotificationTypeToString(NotificationType type)
201 {
202         switch (type) {
203                 case NotificationDowntimeStart:
204                         return "DOWNTIMESTART";
205                 case NotificationDowntimeEnd:
206                         return "DOWNTIMEEND";
207                 case NotificationDowntimeRemoved:
208                         return "DOWNTIMECANCELLED";
209                 case NotificationCustom:
210                         return "CUSTOM";
211                 case NotificationAcknowledgement:
212                         return "ACKNOWLEDGEMENT";
213                 case NotificationProblem:
214                         return "PROBLEM";
215                 case NotificationRecovery:
216                         return "RECOVERY";
217                 case NotificationFlappingStart:
218                         return "FLAPPINGSTART";
219                 case NotificationFlappingEnd:
220                         return "FLAPPINGEND";
221                 default:
222                         return "UNKNOWN_NOTIFICATION";
223         }
224 }
225
226 void Notification::BeginExecuteNotification(NotificationType type, const CheckResult::Ptr& cr, bool force, const String& author, const String& text)
227 {
228         ASSERT(!OwnsLock());
229
230         Checkable::Ptr checkable = GetCheckable();
231
232         if (!force) {
233                 TimePeriod::Ptr tp = GetPeriod();
234
235                 if (tp && !tp->IsInside(Utility::GetTime())) {
236                         Log(LogNotice, "Notification")
237                             << "Not sending notifications for notification object '" << GetName() << "': not in timeperiod";
238                         return;
239                 }
240
241                 double now = Utility::GetTime();
242                 Dictionary::Ptr times = GetTimes();
243
244                 if (type == NotificationProblem) {
245                         if (times && times->Contains("begin") && now < checkable->GetLastHardStateChange() + times->Get("begin")) {
246                                 Log(LogNotice, "Notification")
247                                     << "Not sending notifications for notification object '" << GetName() << "': before escalation range";
248                                 return;
249                         }
250
251                         if (times && times->Contains("end") && now > checkable->GetLastHardStateChange() + times->Get("end")) {
252                                 Log(LogNotice, "Notification")
253                                     << "Not sending notifications for notification object '" << GetName() << "': after escalation range";
254                                 return;
255                         }
256                 }
257
258                 unsigned long ftype = 1 << type;
259
260                 Log(LogDebug, "Notification")
261                     << "FType=" << ftype << ", TypeFilter=" << GetTypeFilter();
262
263                 if (!(ftype & GetTypeFilter())) {
264                         Log(LogNotice, "Notification")
265                             << "Not sending notifications for notification object '" << GetName() << "': type filter does not match";
266                         return;
267                 }
268
269                 Host::Ptr host;
270                 Service::Ptr service;
271                 tie(host, service) = GetHostService(checkable);
272
273                 unsigned long fstate;
274
275                 if (service)
276                         fstate = ServiceStateToFilter(service->GetState());
277                 else
278                         fstate = HostStateToFilter(host->GetState());
279
280                 if (!(fstate & GetStateFilter())) {
281                         Log(LogNotice, "Notification")
282                             << "Not sending notifications for notification object '" << GetName() << "': state filter does not match";
283                         return;
284                 }
285         }
286
287         {
288                 ObjectLock olock(this);
289
290                 double now = Utility::GetTime();
291                 SetLastNotification(now);
292
293                 if (type == NotificationProblem)
294                         SetLastProblemNotification(now);
295         }
296
297         std::set<User::Ptr> allUsers;
298
299         std::set<User::Ptr> users = GetUsers();
300         std::copy(users.begin(), users.end(), std::inserter(allUsers, allUsers.begin()));
301
302         BOOST_FOREACH(const UserGroup::Ptr& ug, GetUserGroups()) {
303                 std::set<User::Ptr> members = ug->GetMembers();
304                 std::copy(members.begin(), members.end(), std::inserter(allUsers, allUsers.begin()));
305         }
306
307         Service::OnNotificationSendStart(this, checkable, allUsers, type, cr, author, text);
308
309         std::set<User::Ptr> allNotifiedUsers;
310         BOOST_FOREACH(const User::Ptr& user, allUsers) {
311                 if (!user->GetEnableNotifications() || !CheckNotificationUserFilters(type, user, force))
312                         continue;
313
314                 Log(LogInformation, "Notification")
315                     << "Sending notification for user '" << user->GetName() << "'";
316
317                 Utility::QueueAsyncCallback(boost::bind(&Notification::ExecuteNotificationHelper, this, type, user, cr, force, author, text));
318
319                 /* collect all notified users */
320                 allNotifiedUsers.insert(user);
321         }
322
323         /* used in db_ido for notification history */
324         Service::OnNotificationSentToAllUsers(this, checkable, allNotifiedUsers, type, cr, author, text);
325 }
326
327 bool Notification::CheckNotificationUserFilters(NotificationType type, const User::Ptr& user, bool force)
328 {
329         ASSERT(!OwnsLock());
330
331         if (!force) {
332                 TimePeriod::Ptr tp = user->GetPeriod();
333
334                 if (tp && !tp->IsInside(Utility::GetTime())) {
335                         Log(LogNotice, "Notification")
336                             << "Not sending notifications for notification object '"
337                             << GetName() << " and user '" << user->GetName() << "': user not in timeperiod";
338                         return false;
339                 }
340
341                 unsigned long ftype = 1 << type;
342
343                 if (!(ftype & user->GetTypeFilter())) {
344                         Log(LogNotice, "Notification")
345                             << "Not sending notifications for notification object '"
346                             << GetName() << " and user '" << user->GetName() << "': type filter does not match";
347                         return false;
348                 }
349
350                 Checkable::Ptr checkable = GetCheckable();
351                 Host::Ptr host;
352                 Service::Ptr service;
353                 tie(host, service) = GetHostService(checkable);
354
355                 unsigned long fstate;
356
357                 if (service)
358                                 fstate = ServiceStateToFilter(service->GetState());
359                 else
360                                 fstate = HostStateToFilter(host->GetState());
361
362                 if (!(fstate & user->GetStateFilter())) {
363                         Log(LogNotice, "Notification")
364                             << "Not sending notifications for notification object '"
365                             << GetName() << " and user '" << user->GetName() << "': state filter does not match";
366                         return false;
367                 }
368         }
369
370         return true;
371 }
372
373 void Notification::ExecuteNotificationHelper(NotificationType type, const User::Ptr& user, const CheckResult::Ptr& cr, bool force, const String& author, const String& text)
374 {
375         ASSERT(!OwnsLock());
376
377         try {
378                 NotificationCommand::Ptr command = GetCommand();
379
380                 if (!command) {
381                         Log(LogDebug, "Notification")
382                             << "No notification_command found for notification '" << GetName() << "'. Skipping execution.";
383                         return;
384                 }
385
386                 command->Execute(this, user, cr, type, author, text);
387
388                 {
389                         ObjectLock olock(this);
390                         UpdateNotificationNumber();
391                         SetLastNotification(Utility::GetTime());
392                 }
393
394                 /* required by compatlogger */
395                 Service::OnNotificationSentToUser(this, GetCheckable(), user, type, cr, author, text, command->GetName());
396
397                 Log(LogInformation, "Notification")
398                     << "Completed sending notification for object '" << GetCheckable()->GetName() << "'";
399         } catch (const std::exception& ex) {
400                 Log(LogWarning, "Notification")
401                     << "Exception occured during notification for object '"
402                     << GetCheckable()->GetName() << "': " << DiagnosticInformation(ex);
403         }
404 }
405
406 int icinga::ServiceStateToFilter(ServiceState state)
407 {
408         switch (state) {
409                 case ServiceOK:
410                         return StateFilterOK;
411                 case ServiceWarning:
412                         return StateFilterWarning;
413                 case ServiceCritical:
414                         return StateFilterCritical;
415                 case ServiceUnknown:
416                         return StateFilterUnknown;
417                 default:
418                         VERIFY(!"Invalid state type.");
419         }
420 }
421
422 int icinga::HostStateToFilter(HostState state)
423 {
424         switch (state) {
425                 case HostUp:
426                         return StateFilterUp;
427                 case HostDown:
428                         return StateFilterDown;
429                 default:
430                         VERIFY(!"Invalid state type.");
431         }
432 }
433
434 int icinga::FilterArrayToInt(const Array::Ptr& typeFilters, int defaultValue)
435 {
436         Value resultTypeFilter;
437
438         if (!typeFilters)
439                 return defaultValue;
440
441         resultTypeFilter = 0;
442
443         ObjectLock olock(typeFilters);
444         BOOST_FOREACH(const Value& typeFilter, typeFilters) {
445                 resultTypeFilter = resultTypeFilter | typeFilter;
446         }
447
448         return resultTypeFilter;
449 }
450
451 void Notification::ValidateFilters(const String& location, const Dictionary::Ptr& attrs)
452 {
453         int sfilter = FilterArrayToInt(attrs->Get("states"), 0);
454
455         if (attrs->Get("service_name") == Empty && (sfilter & ~(StateFilterUp | StateFilterDown)) != 0) {
456                 ConfigCompilerContext::GetInstance()->AddMessage(true, "Validation failed for " +
457                     location + ": State filter is invalid.");
458         }
459
460         if (attrs->Get("service_name") != Empty && (sfilter & ~(StateFilterOK | StateFilterWarning | StateFilterCritical | StateFilterUnknown)) != 0) {
461                 ConfigCompilerContext::GetInstance()->AddMessage(true, "Validation failed for " +
462                     location + ": State filter is invalid.");
463         }
464
465         int tfilter = FilterArrayToInt(attrs->Get("types"), 0);
466
467         if ((tfilter & ~(1 << NotificationDowntimeStart | 1 << NotificationDowntimeEnd | 1 << NotificationDowntimeRemoved |
468             1 << NotificationCustom | 1 << NotificationAcknowledgement | 1 << NotificationProblem | 1 << NotificationRecovery |
469             1 << NotificationFlappingStart | 1 << NotificationFlappingEnd)) != 0) {
470                 ConfigCompilerContext::GetInstance()->AddMessage(true, "Validation failed for " +
471                     location + ": Type filter is invalid.");
472         }
473 }
474