1 /******************************************************************************
3 * Copyright (C) 2012-2018 Icinga Development Team (https://www.icinga.com/) *
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. *
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. *
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 ******************************************************************************/
20 #include "icinga/timeperiod.hpp"
21 #include "icinga/timeperiod-ti.cpp"
22 #include "icinga/legacytimeperiod.hpp"
23 #include "base/configtype.hpp"
24 #include "base/objectlock.hpp"
25 #include "base/exception.hpp"
26 #include "base/logger.hpp"
27 #include "base/timer.hpp"
28 #include "base/utility.hpp"
29 #include <boost/thread/once.hpp>
31 using namespace icinga;
33 REGISTER_TYPE(TimePeriod);
35 static Timer::Ptr l_UpdateTimer;
37 void TimePeriod::Start(bool runtimeCreated)
39 ObjectImpl<TimePeriod>::Start(runtimeCreated);
41 static boost::once_flag once = BOOST_ONCE_INIT;
43 boost::call_once(once, [this]() {
44 l_UpdateTimer = new Timer();
45 l_UpdateTimer->SetInterval(300);
46 l_UpdateTimer->OnTimerExpired.connect(std::bind(&TimePeriod::UpdateTimerHandler));
47 l_UpdateTimer->Start();
50 /* Pre-fill the time period for the next 24 hours. */
51 double now = Utility::GetTime();
52 UpdateRegion(now, now + 24 * 3600, true);
58 void TimePeriod::AddSegment(double begin, double end)
62 Log(LogDebug, "TimePeriod")
63 << "Adding segment '" << Utility::FormatDateTime("%c", begin) << "' <-> '"
64 << Utility::FormatDateTime("%c", end) << "' to TimePeriod '" << GetName() << "'";
66 if (GetValidBegin().IsEmpty() || begin < GetValidBegin())
69 if (GetValidEnd().IsEmpty() || end > GetValidEnd())
72 Array::Ptr segments = GetSegments();
75 /* Try to merge the new segment into an existing segment. */
76 ObjectLock dlock(segments);
77 for (const Dictionary::Ptr& segment : segments) {
78 if (segment->Get("begin") <= begin && segment->Get("end") >= end)
79 return; /* New segment is fully contained in this segment. */
81 if (segment->Get("begin") >= begin && segment->Get("end") <= end) {
82 segment->Set("begin", begin);
83 segment->Set("end", end); /* Extend an existing segment to both sides */
87 if (segment->Get("end") >= begin && segment->Get("end") <= end) {
88 segment->Set("end", end); /* Extend an existing segment to right. */
92 if (segment->Get("begin") >= begin && segment->Get("begin") <= end) {
93 segment->Set("begin", begin); /* Extend an existing segment to left. */
100 /* Create new segment if we weren't able to merge this into an existing segment. */
101 Dictionary::Ptr segment = new Dictionary({
107 segments = new Array();
108 SetSegments(segments);
111 segments->Add(segment);
114 void TimePeriod::AddSegment(const Dictionary::Ptr& segment)
116 AddSegment(segment->Get("begin"), segment->Get("end"));
119 void TimePeriod::RemoveSegment(double begin, double end)
123 Log(LogDebug, "TimePeriod")
124 << "Removing segment '" << Utility::FormatDateTime("%c", begin) << "' <-> '"
125 << Utility::FormatDateTime("%c", end) << "' from TimePeriod '" << GetName() << "'";
127 if (GetValidBegin().IsEmpty() || begin < GetValidBegin())
128 SetValidBegin(begin);
130 if (GetValidEnd().IsEmpty() || end > GetValidEnd())
133 Array::Ptr segments = GetSegments();
138 Array::Ptr newSegments = new Array();
140 /* Try to split or adjust an existing segment. */
141 ObjectLock dlock(segments);
142 for (const Dictionary::Ptr& segment : segments) {
143 /* Fully contained in the specified range? */
144 if (segment->Get("begin") >= begin && segment->Get("end") <= end)
147 /* Not overlapping at all? */
148 if (segment->Get("end") < begin || segment->Get("begin") > end) {
149 newSegments->Add(segment);
155 if (segment->Get("begin") < begin && segment->Get("end") > end) {
156 newSegments->Add(new Dictionary({
157 { "begin", segment->Get("begin") },
161 newSegments->Add(new Dictionary({
163 { "end", segment->Get("end") }
167 /* Adjust the begin/end timestamps so as to not overlap with the specified range. */
168 if (segment->Get("begin") > begin && segment->Get("begin") < end)
169 segment->Set("begin", end);
171 if (segment->Get("end") > begin && segment->Get("end") < end)
172 segment->Set("end", begin);
174 newSegments->Add(segment);
177 SetSegments(newSegments);
184 void TimePeriod::RemoveSegment(const Dictionary::Ptr& segment)
186 RemoveSegment(segment->Get("begin"), segment->Get("end"));
189 void TimePeriod::PurgeSegments(double end)
193 Log(LogDebug, "TimePeriod")
194 << "Purging segments older than '" << Utility::FormatDateTime("%c", end)
195 << "' from TimePeriod '" << GetName() << "'";
197 if (GetValidBegin().IsEmpty() || end < GetValidBegin())
202 Array::Ptr segments = GetSegments();
207 Array::Ptr newSegments = new Array();
209 /* Remove old segments. */
210 ObjectLock dlock(segments);
211 for (const Dictionary::Ptr& segment : segments) {
212 if (segment->Get("end") >= end)
213 newSegments->Add(segment);
216 SetSegments(newSegments);
219 void TimePeriod::Merge(const TimePeriod::Ptr& timeperiod, bool include)
221 Log(LogDebug, "TimePeriod")
222 << "Merge TimePeriod '" << GetName() << "' with '" << timeperiod->GetName() << "' "
223 << "Method: " << (include ? "include" : "exclude");
225 Array::Ptr segments = timeperiod->GetSegments();
228 ObjectLock dlock(segments);
229 ObjectLock ilock(this);
230 for (const Dictionary::Ptr& segment : segments) {
231 include ? AddSegment(segment) : RemoveSegment(segment);
236 void TimePeriod::UpdateRegion(double begin, double end, bool clearExisting)
238 if (!clearExisting) {
239 if (begin < GetValidEnd())
240 begin = GetValidEnd();
242 if (end < GetValidEnd())
246 Array::Ptr segments = GetUpdate()->Invoke({ this, begin, end });
249 ObjectLock olock(this);
250 RemoveSegment(begin, end);
253 ObjectLock dlock(segments);
254 for (const Dictionary::Ptr& segment : segments) {
260 bool preferInclude = GetPreferIncludes();
262 /* First handle the non preferred timeranges */
263 Array::Ptr timeranges = preferInclude ? GetExcludes() : GetIncludes();
266 ObjectLock olock(timeranges);
267 for (const String& name : timeranges) {
268 const TimePeriod::Ptr timeperiod = TimePeriod::GetByName(name);
271 Merge(timeperiod, !preferInclude);
275 /* Preferred timeranges must be handled at the end */
276 timeranges = preferInclude ? GetIncludes() : GetExcludes();
279 ObjectLock olock(timeranges);
280 for (const String& name : timeranges) {
281 const TimePeriod::Ptr timeperiod = TimePeriod::GetByName(name);
284 Merge(timeperiod, preferInclude);
289 bool TimePeriod::GetIsInside() const
291 return IsInside(Utility::GetTime());
294 bool TimePeriod::IsInside(double ts) const
296 ObjectLock olock(this);
298 if (GetValidBegin().IsEmpty() || ts < GetValidBegin() || GetValidEnd().IsEmpty() || ts > GetValidEnd())
299 return true; /* Assume that all invalid regions are "inside". */
301 Array::Ptr segments = GetSegments();
304 ObjectLock dlock(segments);
305 for (const Dictionary::Ptr& segment : segments) {
306 if (ts > segment->Get("begin") && ts < segment->Get("end"))
314 double TimePeriod::FindNextTransition(double begin)
316 ObjectLock olock(this);
318 Array::Ptr segments = GetSegments();
320 double closestTransition = -1;
323 ObjectLock dlock(segments);
324 for (const Dictionary::Ptr& segment : segments) {
325 if (segment->Get("begin") > begin && (segment->Get("begin") < closestTransition || closestTransition == -1))
326 closestTransition = segment->Get("begin");
328 if (segment->Get("end") > begin && (segment->Get("end") < closestTransition || closestTransition == -1))
329 closestTransition = segment->Get("end");
333 return closestTransition;
336 void TimePeriod::UpdateTimerHandler()
338 double now = Utility::GetTime();
340 for (const TimePeriod::Ptr& tp : ConfigType::GetObjectsByType<TimePeriod>()) {
347 ObjectLock olock(tp);
348 tp->PurgeSegments(now - 3600);
350 valid_end = tp->GetValidEnd();
353 tp->UpdateRegion(valid_end, now + 24 * 3600, false);
360 void TimePeriod::Dump()
362 Array::Ptr segments = GetSegments();
364 Log(LogDebug, "TimePeriod")
365 << "Dumping TimePeriod '" << GetName() << "'";
367 Log(LogDebug, "TimePeriod")
368 << "Valid from '" << Utility::FormatDateTime("%c", GetValidBegin())
369 << "' until '" << Utility::FormatDateTime("%c", GetValidEnd());
372 ObjectLock dlock(segments);
373 for (const Dictionary::Ptr& segment : segments) {
374 Log(LogDebug, "TimePeriod")
375 << "Segment: " << Utility::FormatDateTime("%c", segment->Get("begin")) << " <-> "
376 << Utility::FormatDateTime("%c", segment->Get("end"));
380 Log(LogDebug, "TimePeriod", "---");
383 void TimePeriod::ValidateRanges(const Lazy<Dictionary::Ptr>& lvalue, const ValidationUtils& utils)
388 /* create a fake time environment to validate the definitions */
389 time_t refts = Utility::GetTime();
390 tm reference = Utility::LocalTime(refts);
391 Array::Ptr segments = new Array();
393 ObjectLock olock(lvalue());
394 for (const Dictionary::Pair& kv : lvalue()) {
398 LegacyTimePeriod::ParseTimeRange(kv.first, &begin_tm, &end_tm, &stride, &reference);
399 } catch (const std::exception& ex) {
400 BOOST_THROW_EXCEPTION(ValidationError(this, { "ranges" }, "Invalid time specification '" + kv.first + "': " + ex.what()));
404 LegacyTimePeriod::ProcessTimeRanges(kv.second, &reference, segments);
405 } catch (const std::exception& ex) {
406 BOOST_THROW_EXCEPTION(ValidationError(this, { "ranges" }, "Invalid time range definition '" + kv.second + "': " + ex.what()));