diff --git a/AUTHORS b/AUTHORS index 1ab12b828..804b94bf5 100644 --- a/AUTHORS +++ b/AUTHORS @@ -71,6 +71,7 @@ Paul Richards Per von Zweigbergk Petr Ruzicka Phil Hutchinson +Philipp Dallig Ralph Breier Reto Zeder Ricardo Bartels diff --git a/doc/5-advanced-topics.md b/doc/5-advanced-topics.md index de13ca4df..ebd72552b 100644 --- a/doc/5-advanced-topics.md +++ b/doc/5-advanced-topics.md @@ -132,8 +132,9 @@ re-notify if the problem persists. ## Time Periods -Time Periods define time ranges in Icinga where event actions are -triggered, for example whether a service check is executed or not within +[Time Periods](6-object-types.md#objecttype-timeperiod) define +time ranges in Icinga where event actions are triggered, for +example whether a service check is executed or not within the `check_period` attribute. Or a notification should be sent to users or not, filtered by the `period` and `notification_period` configuration attributes for `Notification` and `User` objects. @@ -208,6 +209,67 @@ Use the `period` attribute to assign time periods to period = "workhours" } +### Time Periods Inclusion and Exclusion + +Sometimes it is necessary to exclude certain time ranges from +your default time period definitions. For example if you don't +want to send out any notification during the holiday season, +or if you only want to allow small time windows for executed checks. + +The [TimePeriod object](6-object-types.md#objecttype-timeperiod) +provides the `includes` and `excludes` attributes to solve this issue. +`prefer_includes` defines whether included or excluded time periods are +preferred. + +The following example defines a time period called `holidays` where +notifications should be supressed: + + object TimePeriod "holidays" { + import "legacy-timeperiod" + + ranges = { + "january 1" = "00:00-24:00" //new year's day + "july 4" = "00:00-24:00" //independence day + "december 25" = "00:00-24:00" //christmas + "december 31" = "18:00-24:00" //new year's eve (6pm+) + "2017-04-16" = "00:00-24:00" //easter 2017 + "monday -1 may" = "00:00-24:00" //memorial day (last monday in may) + "monday 1 september" = "00:00-24:00" //labor day (1st monday in september) + "thursday 4 november" = "00:00-24:00" //thanksgiving (4th thursday in november) + } + } + +In addition to that the time period `weekends` defines an additional +time window which should be excluded from notifications: + + object TimePeriod "weekends-excluded" { + import "legacy-timeperiod" + + ranges = { + "saturday" = "00:00-09:00,18:00-24:00" + "sunday" = "00:00-09:00,18:00-24:00" + } + } + +The time period `prod-notification` defines the default time ranges +and adds the excluded time period names as an array. + + object TimePeriod "prod-notification" { + import "legacy-timeperiod" + + excludes = [ "holidays", "weekends-excluded" ] + + ranges = { + "monday" = "00:00-24:00" + "tuesday" = "00:00-24:00" + "wednesday" = "00:00-24:00" + "thursday" = "00:00-24:00" + "friday" = "00:00-24:00" + "saturday" = "00:00-24:00" + "sunday" = "00:00-24:00" + } + } + ## Use Functions in Object Configuration diff --git a/doc/6-object-types.md b/doc/6-object-types.md index 1518a62de..a07a1a146 100644 --- a/doc/6-object-types.md +++ b/doc/6-object-types.md @@ -1380,6 +1380,9 @@ Configuration Attributes: display_name |**Optional.** A short description of the time period. update |**Required.** The "update" script method takes care of updating the internal representation of the time period. In virtually all cases you should import the "legacy-timeperiod" template to take care of this setting. ranges |**Required.** A dictionary containing information which days and durations apply to this timeperiod. + prefer_includes |**Optional.** Boolean whether to prefer timeperiods `includes` or `excludes`. Default to true. + excludes |**Optional.** An array of timeperiods, which should exclude from your timerange. + includes |**Optional.** An array of timeperiods, which should include into your timerange The `/etc/icinga2/conf.d/timeperiods.conf` file is usually used to define timeperiods including this one. diff --git a/lib/checker/checkercomponent.cpp b/lib/checker/checkercomponent.cpp index bfb997fb6..f335a9147 100644 --- a/lib/checker/checkercomponent.cpp +++ b/lib/checker/checkercomponent.cpp @@ -162,7 +162,8 @@ void CheckerComponent::CheckThreadProc(void) if (tp && !tp->IsInside(Utility::GetTime())) { Log(LogNotice, "CheckerComponent") - << "Skipping check for object '" << checkable->GetName() << "': not in check_period"; + << "Skipping check for object '" << checkable->GetName() + << "': not in check period '" << tp->GetName() << "'"; check = false; } } diff --git a/lib/icinga/notification.cpp b/lib/icinga/notification.cpp index 1cfefa0f9..7d1f7247d 100644 --- a/lib/icinga/notification.cpp +++ b/lib/icinga/notification.cpp @@ -250,7 +250,8 @@ void Notification::BeginExecuteNotification(NotificationType type, const CheckRe if (tp && !tp->IsInside(Utility::GetTime())) { Log(LogNotice, "Notification") - << "Not sending notifications for notification object '" << GetName() << "': not in timeperiod"; + << "Not sending notifications for notification object '" << GetName() + << "': not in timeperiod '" << tp->GetName() << "'"; return; } @@ -402,7 +403,8 @@ bool Notification::CheckNotificationUserFilters(NotificationType type, const Use if (tp && !tp->IsInside(Utility::GetTime())) { Log(LogNotice, "Notification") << "Not sending notifications for notification object '" - << GetName() << " and user '" << user->GetName() << "': user not in timeperiod"; + << GetName() << " and user '" << user->GetName() + << "': user period not in timeperiod '" << tp->GetName() << "'"; return false; } diff --git a/lib/icinga/timeperiod.cpp b/lib/icinga/timeperiod.cpp index be2a6fd2a..9899f6d34 100644 --- a/lib/icinga/timeperiod.cpp +++ b/lib/icinga/timeperiod.cpp @@ -77,15 +77,22 @@ void TimePeriod::AddSegment(double begin, double end) if (segment->Get("begin") <= begin && segment->Get("end") >= end) return; /* New segment is fully contained in this segment. */ - if (segment->Get("begin") <= begin && segment->Get("end") >= begin) { - segment->Set("end", end); /* Extend an existing segment. */ + if (segment->Get("begin") >= begin && segment->Get("end") <= end) { + segment->Set("begin", begin); + segment->Set("end", end); /* Extend an existing segment to both sides */ + return; + } + + if (segment->Get("end") >= begin && segment->Get("end") <= end) { + segment->Set("end", end); /* Extend an existing segment to right. */ return; } if (segment->Get("begin") >= begin && segment->Get("begin") <= end) { - segment->Set("begin", begin); /* Extend an existing segment. */ + segment->Set("begin", begin); /* Extend an existing segment to left. */ return; } + } } @@ -142,6 +149,21 @@ void TimePeriod::RemoveSegment(double begin, double end) continue; } + /* Cut between */ + if (segment->Get("begin") < begin && segment->Get("end") > end) { + Dictionary::Ptr firstsegment = new Dictionary(); + firstsegment->Set("begin", segment->Get("begin")); + firstsegment->Set("end", begin); + + Dictionary::Ptr secondsegment = new Dictionary(); + secondsegment->Set("begin", end); + secondsegment->Set("end", segment->Get("end")); + + newSegments->Add(firstsegment); + newSegments->Add(secondsegment); + continue; + } + /* Adjust the begin/end timestamps so as to not overlap with the specified range. */ if (segment->Get("begin") > begin && segment->Get("begin") < end) segment->Set("begin", end); @@ -157,6 +179,11 @@ void TimePeriod::RemoveSegment(double begin, double end) Dump(); } +void TimePeriod::RemoveSegment(const Dictionary::Ptr& segment) +{ + RemoveSegment(segment->Get("begin"), segment->Get("end")); +} + void TimePeriod::PurgeSegments(double end) { ASSERT(OwnsLock()); @@ -187,6 +214,23 @@ void TimePeriod::PurgeSegments(double end) SetSegments(newSegments); } +void TimePeriod::Merge(const TimePeriod::Ptr& timeperiod, bool include) +{ + Log(LogDebug, "TimePeriod") + << "Merge TimePeriod '" << GetName() << "' with '" << timeperiod->GetName() << "' " + << "Method: " << (include ? "include" : "exclude"); + + Array::Ptr segments = timeperiod->GetSegments(); + + if (segments) { + ObjectLock dlock(segments); + ObjectLock ilock(this); + BOOST_FOREACH(const Dictionary::Ptr& segment, segments) { + include ? AddSegment(segment) : RemoveSegment(segment); + } + } +} + void TimePeriod::UpdateRegion(double begin, double end, bool clearExisting) { if (!clearExisting) { @@ -215,6 +259,34 @@ void TimePeriod::UpdateRegion(double begin, double end, bool clearExisting) } } } + + bool preferInclude = GetPreferIncludes(); + + /* First handle the non preferred timeranges */ + Array::Ptr timeranges = preferInclude ? GetExcludes() : GetIncludes(); + + if (timeranges) { + ObjectLock olock(timeranges); + BOOST_FOREACH(const String& name, timeranges) { + const TimePeriod::Ptr timeperiod = TimePeriod::GetByName(name); + + if (timeperiod) + Merge(timeperiod, !preferInclude); + } + } + + /* Preferred timeranges must be handled at the end */ + timeranges = preferInclude ? GetIncludes() : GetExcludes(); + + if (timeranges) { + ObjectLock olock(timeranges); + BOOST_FOREACH(const String& name, timeranges) { + const TimePeriod::Ptr timeperiod = TimePeriod::GetByName(name); + + if (timeperiod) + Merge(timeperiod, preferInclude); + } + } } bool TimePeriod::GetIsInside(void) const diff --git a/lib/icinga/timeperiod.hpp b/lib/icinga/timeperiod.hpp index 51100f575..9b6c88101 100644 --- a/lib/icinga/timeperiod.hpp +++ b/lib/icinga/timeperiod.hpp @@ -54,8 +54,11 @@ private: void AddSegment(double s, double end); void AddSegment(const Dictionary::Ptr& segment); void RemoveSegment(double begin, double end); + void RemoveSegment(const Dictionary::Ptr& segment); void PurgeSegments(double end); + void Merge(const TimePeriod::Ptr& timeperiod, bool include = true); + void Dump(void); static void UpdateTimerHandler(void); diff --git a/lib/icinga/timeperiod.ti b/lib/icinga/timeperiod.ti index 2b78b516e..50b3a771b 100644 --- a/lib/icinga/timeperiod.ti +++ b/lib/icinga/timeperiod.ti @@ -37,6 +37,15 @@ class TimePeriod : CustomVarObject }; [config] Dictionary::Ptr ranges; [config, required] Function::Ptr update; + [config] bool prefer_includes { + default {{{ return true; }}} + }; + [config] array(name(TimePeriod)) excludes { + default {{{ return new Array(); }}} + }; + [config] array(name(TimePeriod)) includes { + default {{{ return new Array(); }}} + }; [state, no_user_modify] Value valid_begin; [state, no_user_modify] Value valid_end; [state, no_user_modify] Array::Ptr segments;