From 54e1c8a9d596cc3092614f0e5d2b425f7a00f83a Mon Sep 17 00:00:00 2001 From: Philipp Dallig Date: Fri, 25 Mar 2016 12:55:11 +0100 Subject: [PATCH] Implement exclude and include ability for TimePeriod objects This feature allows to exclude and include specific time period objects and their time ranges from an existing time period object. This comes in handy when e.g. excluding holidays. fixes #7355 Signed-off-by: Michael Friedrich --- AUTHORS | 1 + doc/5-advanced-topics.md | 66 ++++++++++++++++++++++++++- doc/6-object-types.md | 3 ++ lib/checker/checkercomponent.cpp | 3 +- lib/icinga/notification.cpp | 6 ++- lib/icinga/timeperiod.cpp | 78 ++++++++++++++++++++++++++++++-- lib/icinga/timeperiod.hpp | 3 ++ lib/icinga/timeperiod.ti | 9 ++++ 8 files changed, 161 insertions(+), 8 deletions(-) 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;