diff --git a/lib/icinga/legacytimeperiod.cpp b/lib/icinga/legacytimeperiod.cpp index 777a1cc10eb..7751a7e02e5 100644 --- a/lib/icinga/legacytimeperiod.cpp +++ b/lib/icinga/legacytimeperiod.cpp @@ -388,6 +388,56 @@ void LegacyTimePeriod::ProcessTimeRanges(const String& timeranges, tm *reference } } +Dictionary::Ptr LegacyTimePeriod::FindRunningSegment(const String& daydef, const String& timeranges, tm *reference) +{ + tm begin, end, iter; + time_t tsend, tsiter, tsref; + int stride; + + tsref = mktime(reference); + + ParseTimeRange(daydef, &begin, &end, &stride, reference); + + iter = begin; + + tsend = mktime(&end); + + do { + if (IsInTimeRange(&begin, &end, stride, &iter)) { + Array::Ptr segments = new Array(); + ProcessTimeRanges(timeranges, &iter, segments); + + Dictionary::Ptr bestSegment; + double bestEnd; + + ObjectLock olock(segments); + for (const Dictionary::Ptr& segment : segments) { + double begin = segment->Get("begin"); + double end = segment->Get("end"); + + if (begin >= tsref || end < tsref) + continue; + + if (!bestSegment || end > bestEnd) { + bestSegment = segment; + bestEnd = end; + } + } + + if (bestSegment) + return bestSegment; + } + + iter.tm_mday++; + iter.tm_hour = 0; + iter.tm_min = 0; + iter.tm_sec = 0; + tsiter = mktime(&iter); + } while (tsiter < tsend); + + return nullptr; +} + Dictionary::Ptr LegacyTimePeriod::FindNextSegment(const String& daydef, const String& timeranges, tm *reference) { tm begin, end, iter, ref; diff --git a/lib/icinga/legacytimeperiod.hpp b/lib/icinga/legacytimeperiod.hpp index 9bec5b68c37..57f3cae7529 100644 --- a/lib/icinga/legacytimeperiod.hpp +++ b/lib/icinga/legacytimeperiod.hpp @@ -48,6 +48,7 @@ class LegacyTimePeriod static Dictionary::Ptr ProcessTimeRange(const String& timerange, tm *reference); static void ProcessTimeRanges(const String& timeranges, tm *reference, const Array::Ptr& result); static Dictionary::Ptr FindNextSegment(const String& daydef, const String& timeranges, tm *reference); + static Dictionary::Ptr FindRunningSegment(const String& daydef, const String& timeranges, tm *reference); private: LegacyTimePeriod(); diff --git a/lib/icinga/scheduleddowntime.cpp b/lib/icinga/scheduleddowntime.cpp index 80d7617ba50..1809343bdc3 100644 --- a/lib/icinga/scheduleddowntime.cpp +++ b/lib/icinga/scheduleddowntime.cpp @@ -116,6 +116,67 @@ Checkable::Ptr ScheduledDowntime::GetCheckable() const return host->GetServiceByShortName(GetServiceName()); } +std::pair ScheduledDowntime::FindRunningSegment(double minEnd) +{ + time_t refts = Utility::GetTime(); + tm reference = Utility::LocalTime(refts); + + Log(LogDebug, "ScheduledDowntime") + << "Finding running scheduled downtime segment for time " << refts + << " (minEnd " << (minEnd > 0 ? Utility::FormatDateTime("%c", minEnd) : "-") << ")"; + + Dictionary::Ptr ranges = GetRanges(); + + if (!ranges) + return std::make_pair(0, 0); + + Array::Ptr segments = new Array(); + + Dictionary::Ptr bestSegment; + double bestBegin, bestEnd; + double now = Utility::GetTime(); + + ObjectLock olock(ranges); + + /* Find the longest lasting (and longer than minEnd, if given) segment that's already running */ + for (const Dictionary::Pair& kv : ranges) { + Log(LogDebug, "ScheduledDowntime") + << "Evaluating (running?) segment: " << kv.first << ": " << kv.second; + + Dictionary::Ptr segment = LegacyTimePeriod::FindRunningSegment(kv.first, kv.second, &reference); + + if (!segment) + continue; + + double begin = segment->Get("begin"); + double end = segment->Get("end"); + + Log(LogDebug, "ScheduledDowntime") + << "Considering (running?) segment: " << Utility::FormatDateTime("%c", begin) << " -> " << Utility::FormatDateTime("%c", end); + + if (begin >= now || end < now) { + Log(LogDebug, "ScheduledDowntime") << "not running."; + continue; + } + if (minEnd && end <= minEnd) { + Log(LogDebug, "ScheduledDowntime") << "ending too early."; + continue; + } + + if (!bestSegment || end > bestEnd) { + Log(LogDebug, "ScheduledDowntime") << "(best match yet)"; + bestSegment = segment; + bestBegin = begin; + bestEnd = end; + } + } + + if (bestSegment) + return std::make_pair(bestBegin, bestEnd); + + return std::make_pair(0, 0); +} + std::pair ScheduledDowntime::FindNextSegment() { time_t refts = Utility::GetTime(); @@ -132,42 +193,55 @@ std::pair ScheduledDowntime::FindNextSegment() Array::Ptr segments = new Array(); Dictionary::Ptr bestSegment; - double bestBegin; + double bestBegin, bestEnd; double now = Utility::GetTime(); ObjectLock olock(ranges); + + /* Find the segment starting earliest */ for (const Dictionary::Pair& kv : ranges) { Log(LogDebug, "ScheduledDowntime") - << "Evaluating segment: " << kv.first << ": " << kv.second << " at "; + << "Evaluating segment: " << kv.first << ": " << kv.second; Dictionary::Ptr segment = LegacyTimePeriod::FindNextSegment(kv.first, kv.second, &reference); if (!segment) continue; - Log(LogDebug, "ScheduledDowntime") - << "Considering segment: " << Utility::FormatDateTime("%c", segment->Get("begin")) << " -> " << Utility::FormatDateTime("%c", segment->Get("end")); - double begin = segment->Get("begin"); + double end = segment->Get("end"); + + Log(LogDebug, "ScheduledDowntime") + << "Considering segment: " << Utility::FormatDateTime("%c", begin) << " -> " << Utility::FormatDateTime("%c", end); - if (begin < now) + if (begin < now) { + Log(LogDebug, "ScheduledDowntime") << "already running."; continue; + } if (!bestSegment || begin < bestBegin) { + Log(LogDebug, "ScheduledDowntime") << "(best match yet)"; bestSegment = segment; bestBegin = begin; + bestEnd = end; } } if (bestSegment) - return std::make_pair(bestSegment->Get("begin"), bestSegment->Get("end")); - else - return std::make_pair(0, 0); + return std::make_pair(bestBegin, bestEnd); + + return std::make_pair(0, 0); } void ScheduledDowntime::CreateNextDowntime() { + double minEnd = 0; + for (const Downtime::Ptr& downtime : GetCheckable()->GetDowntimes()) { + double end = downtime->GetEndTime(); + if (end > minEnd) + minEnd = end; + if (downtime->GetScheduledBy() != GetName() || downtime->GetStartTime() < Utility::GetTime()) continue; @@ -179,16 +253,11 @@ void ScheduledDowntime::CreateNextDowntime() Log(LogDebug, "ScheduledDowntime") << "Creating new Downtime for ScheduledDowntime \"" << GetName() << "\""; - std::pair segment = FindNextSegment(); - + std::pair segment = FindRunningSegment(minEnd); if (segment.first == 0 && segment.second == 0) { - tm reference = Utility::LocalTime(Utility::GetTime()); - reference.tm_mday++; - reference.tm_hour = 0; - reference.tm_min = 0; - reference.tm_sec = 0; - - return; + segment = FindNextSegment(); + if (segment.first == 0 && segment.second == 0) + return; } String downtimeName = Downtime::AddDowntime(GetCheckable(), GetAuthor(), GetComment(), @@ -261,4 +330,4 @@ void ScheduledDowntime::ValidateChildOptions(const Lazy& lvalue, const Va } catch (const std::exception&) { BOOST_THROW_EXCEPTION(ValidationError(this, { "child_options" }, "Invalid child_options specified")); } -} \ No newline at end of file +} diff --git a/lib/icinga/scheduleddowntime.hpp b/lib/icinga/scheduleddowntime.hpp index 7fbcd1e126b..a02ff6de2ff 100644 --- a/lib/icinga/scheduleddowntime.hpp +++ b/lib/icinga/scheduleddowntime.hpp @@ -58,6 +58,7 @@ class ScheduledDowntime final : public ObjectImpl private: static void TimerProc(); + std::pair FindRunningSegment(double minEnd = 0); std::pair FindNextSegment(); void CreateNextDowntime();