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