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 <michael.friedrich@netways.de>
This commit is contained in:
Philipp Dallig 2016-03-25 12:55:11 +01:00 committed by Michael Friedrich
parent d49b63d2ab
commit 54e1c8a9d5
8 changed files with 161 additions and 8 deletions

View File

@ -71,6 +71,7 @@ Paul Richards <paul@minimoo.org>
Per von Zweigbergk <pvz@itassistans.se>
Petr Ruzicka <petr.ruzicka@gmail.com>
Phil Hutchinson <phil@volumedia.co.uk>
Philipp Dallig <philipp.dallig@gmail.com>
Ralph Breier <ralph.breier@roedl.com>
Reto Zeder <reto.zeder@arcade.ch>
Ricardo Bartels <ricardo@bitchbrothers.com>

View File

@ -132,8 +132,9 @@ re-notify if the problem persists.
## <a id="timeperiods"></a> 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"
}
### <a id="timeperiods-includes-excludes"></a> 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"
}
}
## <a id="use-functions-object-config"></a> Use Functions in Object Configuration

View File

@ -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.

View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -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

View File

@ -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);

View File

@ -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;