]> granicus.if.org Git - icinga2/blob - lib/icinga/timeperiod.cpp
Merge pull request #5964 from fedepires/fix/opentsdbwriter-host-tag-5963
[icinga2] / lib / icinga / timeperiod.cpp
1 /******************************************************************************
2  * Icinga 2                                                                   *
3  * Copyright (C) 2012-2018 Icinga Development Team (https://www.icinga.com/)  *
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/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>
30
31 using namespace icinga;
32
33 REGISTER_TYPE(TimePeriod);
34
35 static Timer::Ptr l_UpdateTimer;
36
37 void TimePeriod::Start(bool runtimeCreated)
38 {
39         ObjectImpl<TimePeriod>::Start(runtimeCreated);
40
41         static boost::once_flag once = BOOST_ONCE_INIT;
42
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();
48         });
49
50         /* Pre-fill the time period for the next 24 hours. */
51         double now = Utility::GetTime();
52         UpdateRegion(now, now + 24 * 3600, true);
53 #ifdef _DEBUG
54         Dump();
55 #endif /* _DEBUG */
56 }
57
58 void TimePeriod::AddSegment(double begin, double end)
59 {
60         ASSERT(OwnsLock());
61
62         Log(LogDebug, "TimePeriod")
63                 << "Adding segment '" << Utility::FormatDateTime("%c", begin) << "' <-> '"
64                 << Utility::FormatDateTime("%c", end) << "' to TimePeriod '" << GetName() << "'";
65
66         if (GetValidBegin().IsEmpty() || begin < GetValidBegin())
67                 SetValidBegin(begin);
68
69         if (GetValidEnd().IsEmpty() || end > GetValidEnd())
70                 SetValidEnd(end);
71
72         Array::Ptr segments = GetSegments();
73
74         if (segments) {
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. */
80
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 */
84                                 return;
85                         }
86
87                         if (segment->Get("end") >= begin && segment->Get("end") <= end) {
88                                 segment->Set("end", end); /* Extend an existing segment to right. */
89                                 return;
90                         }
91
92                         if (segment->Get("begin") >= begin && segment->Get("begin") <= end) {
93                                 segment->Set("begin", begin); /* Extend an existing segment to left. */
94                                 return;
95                         }
96
97                 }
98         }
99
100         /* Create new segment if we weren't able to merge this into an existing segment. */
101         Dictionary::Ptr segment = new Dictionary({
102                 { "begin", begin },
103                 { "end", end }
104         });
105
106         if (!segments) {
107                 segments = new Array();
108                 SetSegments(segments);
109         }
110
111         segments->Add(segment);
112 }
113
114 void TimePeriod::AddSegment(const Dictionary::Ptr& segment)
115 {
116         AddSegment(segment->Get("begin"), segment->Get("end"));
117 }
118
119 void TimePeriod::RemoveSegment(double begin, double end)
120 {
121         ASSERT(OwnsLock());
122
123         Log(LogDebug, "TimePeriod")
124                 << "Removing segment '" << Utility::FormatDateTime("%c", begin) << "' <-> '"
125                 << Utility::FormatDateTime("%c", end) << "' from TimePeriod '" << GetName() << "'";
126
127         if (GetValidBegin().IsEmpty() || begin < GetValidBegin())
128                 SetValidBegin(begin);
129
130         if (GetValidEnd().IsEmpty() || end > GetValidEnd())
131                 SetValidEnd(end);
132
133         Array::Ptr segments = GetSegments();
134
135         if (!segments)
136                 return;
137
138         Array::Ptr newSegments = new Array();
139
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)
145                         continue;
146
147                 /* Not overlapping at all? */
148                 if (segment->Get("end") < begin || segment->Get("begin") > end) {
149                         newSegments->Add(segment);
150
151                         continue;
152                 }
153
154                 /* Cut between */
155                 if (segment->Get("begin") < begin && segment->Get("end") > end) {
156                         newSegments->Add(new Dictionary({
157                                 { "begin", segment->Get("begin") },
158                                 { "end", begin }
159                         }));
160
161                         newSegments->Add(new Dictionary({
162                                 { "begin", end },
163                                 { "end", segment->Get("end") }
164                         }));
165                 }
166
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);
170
171                 if (segment->Get("end") > begin && segment->Get("end") < end)
172                         segment->Set("end", begin);
173
174                 newSegments->Add(segment);
175         }
176
177         SetSegments(newSegments);
178
179 #ifdef _DEBUG
180         Dump();
181 #endif /* _DEBUG */
182 }
183
184 void TimePeriod::RemoveSegment(const Dictionary::Ptr& segment)
185 {
186         RemoveSegment(segment->Get("begin"), segment->Get("end"));
187 }
188
189 void TimePeriod::PurgeSegments(double end)
190 {
191         ASSERT(OwnsLock());
192
193         Log(LogDebug, "TimePeriod")
194                 << "Purging segments older than '" << Utility::FormatDateTime("%c", end)
195                 << "' from TimePeriod '" << GetName() << "'";
196
197         if (GetValidBegin().IsEmpty() || end < GetValidBegin())
198                 return;
199
200         SetValidBegin(end);
201
202         Array::Ptr segments = GetSegments();
203
204         if (!segments)
205                 return;
206
207         Array::Ptr newSegments = new Array();
208
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);
214         }
215
216         SetSegments(newSegments);
217 }
218
219 void TimePeriod::Merge(const TimePeriod::Ptr& timeperiod, bool include)
220 {
221         Log(LogDebug, "TimePeriod")
222                 << "Merge TimePeriod '" << GetName() << "' with '" << timeperiod->GetName() << "' "
223                 << "Method: " << (include ? "include" : "exclude");
224
225         Array::Ptr segments = timeperiod->GetSegments();
226
227         if (segments) {
228                 ObjectLock dlock(segments);
229                 ObjectLock ilock(this);
230                 for (const Dictionary::Ptr& segment : segments) {
231                         include ? AddSegment(segment) : RemoveSegment(segment);
232                 }
233         }
234 }
235
236 void TimePeriod::UpdateRegion(double begin, double end, bool clearExisting)
237 {
238         if (!clearExisting) {
239                 if (begin < GetValidEnd())
240                         begin = GetValidEnd();
241
242                 if (end < GetValidEnd())
243                         return;
244         }
245
246         Array::Ptr segments = GetUpdate()->Invoke({ this, begin, end });
247
248         {
249                 ObjectLock olock(this);
250                 RemoveSegment(begin, end);
251
252                 if (segments) {
253                         ObjectLock dlock(segments);
254                         for (const Dictionary::Ptr& segment : segments) {
255                                 AddSegment(segment);
256                         }
257                 }
258         }
259
260         bool preferInclude = GetPreferIncludes();
261
262         /* First handle the non preferred timeranges */
263         Array::Ptr timeranges = preferInclude ? GetExcludes() : GetIncludes();
264
265         if (timeranges) {
266                 ObjectLock olock(timeranges);
267                 for (const String& name : timeranges) {
268                         const TimePeriod::Ptr timeperiod = TimePeriod::GetByName(name);
269
270                         if (timeperiod)
271                                 Merge(timeperiod, !preferInclude);
272                 }
273         }
274
275         /* Preferred timeranges must be handled at the end */
276         timeranges = preferInclude ? GetIncludes() : GetExcludes();
277
278         if (timeranges) {
279                 ObjectLock olock(timeranges);
280                 for (const String& name : timeranges) {
281                         const TimePeriod::Ptr timeperiod = TimePeriod::GetByName(name);
282
283                         if (timeperiod)
284                                 Merge(timeperiod, preferInclude);
285                 }
286         }
287 }
288
289 bool TimePeriod::GetIsInside() const
290 {
291         return IsInside(Utility::GetTime());
292 }
293
294 bool TimePeriod::IsInside(double ts) const
295 {
296         ObjectLock olock(this);
297
298         if (GetValidBegin().IsEmpty() || ts < GetValidBegin() || GetValidEnd().IsEmpty() || ts > GetValidEnd())
299                 return true; /* Assume that all invalid regions are "inside". */
300
301         Array::Ptr segments = GetSegments();
302
303         if (segments) {
304                 ObjectLock dlock(segments);
305                 for (const Dictionary::Ptr& segment : segments) {
306                         if (ts > segment->Get("begin") && ts < segment->Get("end"))
307                                 return true;
308                 }
309         }
310
311         return false;
312 }
313
314 double TimePeriod::FindNextTransition(double begin)
315 {
316         ObjectLock olock(this);
317
318         Array::Ptr segments = GetSegments();
319
320         double closestTransition = -1;
321
322         if (segments) {
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");
327
328                         if (segment->Get("end") > begin && (segment->Get("end") < closestTransition || closestTransition == -1))
329                                 closestTransition = segment->Get("end");
330                 }
331         }
332
333         return closestTransition;
334 }
335
336 void TimePeriod::UpdateTimerHandler()
337 {
338         double now = Utility::GetTime();
339
340         for (const TimePeriod::Ptr& tp : ConfigType::GetObjectsByType<TimePeriod>()) {
341                 if (!tp->IsActive())
342                         continue;
343
344                 double valid_end;
345
346                 {
347                         ObjectLock olock(tp);
348                         tp->PurgeSegments(now - 3600);
349
350                         valid_end = tp->GetValidEnd();
351                 }
352
353                 tp->UpdateRegion(valid_end, now + 24 * 3600, false);
354 #ifdef _DEBUG
355                 tp->Dump();
356 #endif /* _DEBUG */
357         }
358 }
359
360 void TimePeriod::Dump()
361 {
362         Array::Ptr segments = GetSegments();
363
364         Log(LogDebug, "TimePeriod")
365                 << "Dumping TimePeriod '" << GetName() << "'";
366
367         Log(LogDebug, "TimePeriod")
368                 << "Valid from '" << Utility::FormatDateTime("%c", GetValidBegin())
369                 << "' until '" << Utility::FormatDateTime("%c", GetValidEnd());
370
371         if (segments) {
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"));
377                 }
378         }
379
380         Log(LogDebug, "TimePeriod", "---");
381 }
382
383 void TimePeriod::ValidateRanges(const Lazy<Dictionary::Ptr>& lvalue, const ValidationUtils& utils)
384 {
385         if (!lvalue())
386                 return;
387
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();
392
393         ObjectLock olock(lvalue());
394         for (const Dictionary::Pair& kv : lvalue()) {
395                 try {
396                         tm begin_tm, end_tm;
397                         int stride;
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()));
401                 }
402
403                 try {
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()));
407                 }
408         }
409 }