]> granicus.if.org Git - icinga2/blob - lib/icinga/timeperiod.cpp
Merge pull request #7164 from Icinga/bugfix/notification-times-validate
[icinga2] / lib / icinga / timeperiod.cpp
1 /* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
2
3 #include "icinga/timeperiod.hpp"
4 #include "icinga/timeperiod-ti.cpp"
5 #include "icinga/legacytimeperiod.hpp"
6 #include "base/configtype.hpp"
7 #include "base/objectlock.hpp"
8 #include "base/exception.hpp"
9 #include "base/logger.hpp"
10 #include "base/timer.hpp"
11 #include "base/utility.hpp"
12 #include <boost/thread/once.hpp>
13
14 using namespace icinga;
15
16 REGISTER_TYPE(TimePeriod);
17
18 static Timer::Ptr l_UpdateTimer;
19
20 void TimePeriod::Start(bool runtimeCreated)
21 {
22         ObjectImpl<TimePeriod>::Start(runtimeCreated);
23
24         static boost::once_flag once = BOOST_ONCE_INIT;
25
26         boost::call_once(once, [this]() {
27                 l_UpdateTimer = new Timer();
28                 l_UpdateTimer->SetInterval(300);
29                 l_UpdateTimer->OnTimerExpired.connect(std::bind(&TimePeriod::UpdateTimerHandler));
30                 l_UpdateTimer->Start();
31         });
32
33         /* Pre-fill the time period for the next 24 hours. */
34         double now = Utility::GetTime();
35         UpdateRegion(now, now + 24 * 3600, true);
36 #ifdef _DEBUG
37         Dump();
38 #endif /* _DEBUG */
39 }
40
41 void TimePeriod::AddSegment(double begin, double end)
42 {
43         ASSERT(OwnsLock());
44
45         Log(LogDebug, "TimePeriod")
46                 << "Adding segment '" << Utility::FormatDateTime("%c", begin) << "' <-> '"
47                 << Utility::FormatDateTime("%c", end) << "' to TimePeriod '" << GetName() << "'";
48
49         if (GetValidBegin().IsEmpty() || begin < GetValidBegin())
50                 SetValidBegin(begin);
51
52         if (GetValidEnd().IsEmpty() || end > GetValidEnd())
53                 SetValidEnd(end);
54
55         Array::Ptr segments = GetSegments();
56
57         if (segments) {
58                 /* Try to merge the new segment into an existing segment. */
59                 ObjectLock dlock(segments);
60                 for (const Dictionary::Ptr& segment : segments) {
61                         if (segment->Get("begin") <= begin && segment->Get("end") >= end)
62                                 return; /* New segment is fully contained in this segment. */
63
64                         if (segment->Get("begin") >= begin && segment->Get("end") <= end) {
65                                 segment->Set("begin", begin);
66                                 segment->Set("end", end); /* Extend an existing segment to both sides */
67                                 return;
68                         }
69
70                         if (segment->Get("end") >= begin && segment->Get("end") <= end) {
71                                 segment->Set("end", end); /* Extend an existing segment to right. */
72                                 return;
73                         }
74
75                         if (segment->Get("begin") >= begin && segment->Get("begin") <= end) {
76                                 segment->Set("begin", begin); /* Extend an existing segment to left. */
77                                 return;
78                         }
79
80                 }
81         }
82
83         /* Create new segment if we weren't able to merge this into an existing segment. */
84         Dictionary::Ptr segment = new Dictionary({
85                 { "begin", begin },
86                 { "end", end }
87         });
88
89         if (!segments) {
90                 segments = new Array();
91                 SetSegments(segments);
92         }
93
94         segments->Add(segment);
95 }
96
97 void TimePeriod::AddSegment(const Dictionary::Ptr& segment)
98 {
99         AddSegment(segment->Get("begin"), segment->Get("end"));
100 }
101
102 void TimePeriod::RemoveSegment(double begin, double end)
103 {
104         ASSERT(OwnsLock());
105
106         Log(LogDebug, "TimePeriod")
107                 << "Removing segment '" << Utility::FormatDateTime("%c", begin) << "' <-> '"
108                 << Utility::FormatDateTime("%c", end) << "' from TimePeriod '" << GetName() << "'";
109
110         if (GetValidBegin().IsEmpty() || begin < GetValidBegin())
111                 SetValidBegin(begin);
112
113         if (GetValidEnd().IsEmpty() || end > GetValidEnd())
114                 SetValidEnd(end);
115
116         Array::Ptr segments = GetSegments();
117
118         if (!segments)
119                 return;
120
121         Array::Ptr newSegments = new Array();
122
123         /* Try to split or adjust an existing segment. */
124         ObjectLock dlock(segments);
125         for (const Dictionary::Ptr& segment : segments) {
126                 /* Fully contained in the specified range? */
127                 if (segment->Get("begin") >= begin && segment->Get("end") <= end)
128                         // Don't add the old segment, because the segment is fully contained into our range
129                         continue;
130
131                 /* Not overlapping at all? */
132                 if (segment->Get("end") < begin || segment->Get("begin") > end) {
133                         newSegments->Add(segment);
134                         continue;
135                 }
136
137                 /* Cut between */
138                 if (segment->Get("begin") < begin && segment->Get("end") > end) {
139                         newSegments->Add(new Dictionary({
140                                 { "begin", segment->Get("begin") },
141                                 { "end", begin }
142                         }));
143
144                         newSegments->Add(new Dictionary({
145                                 { "begin", end },
146                                 { "end", segment->Get("end") }
147                         }));
148                         // Don't add the old segment, because we have now two new segments and a gap between
149                         continue;
150                 }
151
152                 /* Adjust the begin/end timestamps so as to not overlap with the specified range. */
153                 if (segment->Get("begin") > begin && segment->Get("begin") < end)
154                         segment->Set("begin", end);
155
156                 if (segment->Get("end") > begin && segment->Get("end") < end)
157                         segment->Set("end", begin);
158
159                 newSegments->Add(segment);
160         }
161
162         SetSegments(newSegments);
163
164 #ifdef _DEBUG
165         Dump();
166 #endif /* _DEBUG */
167 }
168
169 void TimePeriod::RemoveSegment(const Dictionary::Ptr& segment)
170 {
171         RemoveSegment(segment->Get("begin"), segment->Get("end"));
172 }
173
174 void TimePeriod::PurgeSegments(double end)
175 {
176         ASSERT(OwnsLock());
177
178         Log(LogDebug, "TimePeriod")
179                 << "Purging segments older than '" << Utility::FormatDateTime("%c", end)
180                 << "' from TimePeriod '" << GetName() << "'";
181
182         if (GetValidBegin().IsEmpty() || end < GetValidBegin())
183                 return;
184
185         SetValidBegin(end);
186
187         Array::Ptr segments = GetSegments();
188
189         if (!segments)
190                 return;
191
192         Array::Ptr newSegments = new Array();
193
194         /* Remove old segments. */
195         ObjectLock dlock(segments);
196         for (const Dictionary::Ptr& segment : segments) {
197                 if (segment->Get("end") >= end)
198                         newSegments->Add(segment);
199         }
200
201         SetSegments(newSegments);
202 }
203
204 void TimePeriod::Merge(const TimePeriod::Ptr& timeperiod, bool include)
205 {
206         Log(LogDebug, "TimePeriod")
207                 << "Merge TimePeriod '" << GetName() << "' with '" << timeperiod->GetName() << "' "
208                 << "Method: " << (include ? "include" : "exclude");
209
210         Array::Ptr segments = timeperiod->GetSegments();
211
212         if (segments) {
213                 ObjectLock dlock(segments);
214                 ObjectLock ilock(this);
215                 for (const Dictionary::Ptr& segment : segments) {
216                         include ? AddSegment(segment) : RemoveSegment(segment);
217                 }
218         }
219 }
220
221 void TimePeriod::UpdateRegion(double begin, double end, bool clearExisting)
222 {
223         if (clearExisting) {
224                 SetSegments(new Array());
225         } else {
226                 if (begin < GetValidEnd())
227                         begin = GetValidEnd();
228
229                 if (end < GetValidEnd())
230                         return;
231         }
232
233         Array::Ptr segments = GetUpdate()->Invoke({ this, begin, end });
234
235         {
236                 ObjectLock olock(this);
237                 RemoveSegment(begin, end);
238
239                 if (segments) {
240                         ObjectLock dlock(segments);
241                         for (const Dictionary::Ptr& segment : segments) {
242                                 AddSegment(segment);
243                         }
244                 }
245         }
246
247         bool preferInclude = GetPreferIncludes();
248
249         /* First handle the non preferred timeranges */
250         Array::Ptr timeranges = preferInclude ? GetExcludes() : GetIncludes();
251
252         if (timeranges) {
253                 ObjectLock olock(timeranges);
254                 for (const String& name : timeranges) {
255                         const TimePeriod::Ptr timeperiod = TimePeriod::GetByName(name);
256
257                         if (timeperiod)
258                                 Merge(timeperiod, !preferInclude);
259                 }
260         }
261
262         /* Preferred timeranges must be handled at the end */
263         timeranges = preferInclude ? GetIncludes() : GetExcludes();
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
276 bool TimePeriod::GetIsInside() const
277 {
278         return IsInside(Utility::GetTime());
279 }
280
281 bool TimePeriod::IsInside(double ts) const
282 {
283         ObjectLock olock(this);
284
285         if (GetValidBegin().IsEmpty() || ts < GetValidBegin() || GetValidEnd().IsEmpty() || ts > GetValidEnd())
286                 return true; /* Assume that all invalid regions are "inside". */
287
288         Array::Ptr segments = GetSegments();
289
290         if (segments) {
291                 ObjectLock dlock(segments);
292                 for (const Dictionary::Ptr& segment : segments) {
293                         if (ts > segment->Get("begin") && ts < segment->Get("end"))
294                                 return true;
295                 }
296         }
297
298         return false;
299 }
300
301 double TimePeriod::FindNextTransition(double begin)
302 {
303         ObjectLock olock(this);
304
305         Array::Ptr segments = GetSegments();
306
307         double closestTransition = -1;
308
309         if (segments) {
310                 ObjectLock dlock(segments);
311                 for (const Dictionary::Ptr& segment : segments) {
312                         if (segment->Get("begin") > begin && (segment->Get("begin") < closestTransition || closestTransition == -1))
313                                 closestTransition = segment->Get("begin");
314
315                         if (segment->Get("end") > begin && (segment->Get("end") < closestTransition || closestTransition == -1))
316                                 closestTransition = segment->Get("end");
317                 }
318         }
319
320         return closestTransition;
321 }
322
323 void TimePeriod::UpdateTimerHandler()
324 {
325         double now = Utility::GetTime();
326
327         for (const TimePeriod::Ptr& tp : ConfigType::GetObjectsByType<TimePeriod>()) {
328                 if (!tp->IsActive())
329                         continue;
330
331                 double valid_end;
332
333                 {
334                         ObjectLock olock(tp);
335                         tp->PurgeSegments(now - 3600);
336
337                         valid_end = tp->GetValidEnd();
338                 }
339
340                 tp->UpdateRegion(valid_end, now + 24 * 3600, false);
341 #ifdef _DEBUG
342                 tp->Dump();
343 #endif /* _DEBUG */
344         }
345 }
346
347 void TimePeriod::Dump()
348 {
349         Array::Ptr segments = GetSegments();
350
351         Log(LogDebug, "TimePeriod")
352                 << "Dumping TimePeriod '" << GetName() << "'";
353
354         Log(LogDebug, "TimePeriod")
355                 << "Valid from '" << Utility::FormatDateTime("%c", GetValidBegin())
356                 << "' until '" << Utility::FormatDateTime("%c", GetValidEnd());
357
358         if (segments) {
359                 ObjectLock dlock(segments);
360                 for (const Dictionary::Ptr& segment : segments) {
361                         Log(LogDebug, "TimePeriod")
362                                 << "Segment: " << Utility::FormatDateTime("%c", segment->Get("begin")) << " <-> "
363                                 << Utility::FormatDateTime("%c", segment->Get("end"));
364                 }
365         }
366
367         Log(LogDebug, "TimePeriod", "---");
368 }
369
370 void TimePeriod::ValidateRanges(const Lazy<Dictionary::Ptr>& lvalue, const ValidationUtils& utils)
371 {
372         if (!lvalue())
373                 return;
374
375         /* create a fake time environment to validate the definitions */
376         time_t refts = Utility::GetTime();
377         tm reference = Utility::LocalTime(refts);
378         Array::Ptr segments = new Array();
379
380         ObjectLock olock(lvalue());
381         for (const Dictionary::Pair& kv : lvalue()) {
382                 try {
383                         tm begin_tm, end_tm;
384                         int stride;
385                         LegacyTimePeriod::ParseTimeRange(kv.first, &begin_tm, &end_tm, &stride, &reference);
386                 } catch (const std::exception& ex) {
387                         BOOST_THROW_EXCEPTION(ValidationError(this, { "ranges" }, "Invalid time specification '" + kv.first + "': " + ex.what()));
388                 }
389
390                 try {
391                         LegacyTimePeriod::ProcessTimeRanges(kv.second, &reference, segments);
392                 } catch (const std::exception& ex) {
393                         BOOST_THROW_EXCEPTION(ValidationError(this, { "ranges" }, "Invalid time range definition '" + kv.second + "': " + ex.what()));
394                 }
395         }
396 }