From 076cec6d52245e658338d12357ffe79a3d7f38ab Mon Sep 17 00:00:00 2001 From: efuss Date: Wed, 29 Aug 2018 18:46:52 +0200 Subject: [PATCH 1/7] Add files via upload Put running downtimes in effect and merge segments Put an already running downtime in effect immediately: If Icinga2 was restarted with a newly configured downtime that should be in effect at the time of restart, the should-be-running segment of it was not put into effect. Merge adjacent downtime segments: As legacy time periods can't span midnight, a configured downtime spanning midnight is technically two (immediately adjecent) segments. As segments were queued individually, at midnight, the downtime technically ended (sending a DowntimeEnd) only to start immediately again (sending a DowntimeStart notification). With this fix, an immediately following segment is merged into the current one in case the current one is ending soon (where "soon" is defined as "12 hours or less"). The time limit is arbitrary, but necessary to prevent endless merging in case of a 7*24 downtime. --- lib/icinga/legacytimeperiod.cpp | 50 +++++++++++ lib/icinga/legacytimeperiod.hpp | 1 + lib/icinga/scheduleddowntime.cpp | 141 ++++++++++++++++++++++++++----- lib/icinga/scheduleddowntime.hpp | 3 +- 4 files changed, 175 insertions(+), 20 deletions(-) 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..fa6f69cf939 100644 --- a/lib/icinga/scheduleddowntime.cpp +++ b/lib/icinga/scheduleddowntime.cpp @@ -116,13 +116,14 @@ Checkable::Ptr ScheduledDowntime::GetCheckable() const return host->GetServiceByShortName(GetServiceName()); } -std::pair ScheduledDowntime::FindNextSegment() +std::pair ScheduledDowntime::FindRunningSegment(double minEnd) { time_t refts = Utility::GetTime(); tm reference = Utility::LocalTime(refts); Log(LogDebug, "ScheduledDowntime") - << "Finding next scheduled downtime segment for time " << refts; + << "Finding running scheduled downtime segment for time " << refts + << " (minEnd " << (minEnd > 0 ? Utility::FormatDateTime("%c", minEnd) : "-") << ")"; Dictionary::Ptr ranges = GetRanges(); @@ -132,42 +133,149 @@ 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 longest lasting (at least until minEnd, if given) segment that's already running */ for (const Dictionary::Pair& kv : ranges) { Log(LogDebug, "ScheduledDowntime") - << "Evaluating segment: " << kv.first << ": " << kv.second << " at "; + << "Evaluating (running?) segment: " << kv.first << ": " << kv.second; - Dictionary::Ptr segment = LegacyTimePeriod::FindNextSegment(kv.first, kv.second, &reference); + 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 segment: " << Utility::FormatDateTime("%c", segment->Get("begin")) << " -> " << Utility::FormatDateTime("%c", segment->Get("end")); + << "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(double minBegin) +{ + time_t refts = Utility::GetTime(); + tm reference = Utility::LocalTime(refts); + + Log(LogDebug, "ScheduledDowntime") + << "Finding next scheduled downtime segment for time " << refts + << " (minBegin " << (minBegin > 0 ? Utility::FormatDateTime("%c", minBegin) : "-") << ")"; + + 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 segment starting earliest, but not earlier than minBegin */ + for (const Dictionary::Pair& kv : ranges) { + Log(LogDebug, "ScheduledDowntime") + << "Evaluating segment: " << kv.first << ": " << kv.second; + + Dictionary::Ptr segment = LegacyTimePeriod::FindNextSegment(kv.first, kv.second, &reference); + + if (!segment) + continue; double begin = segment->Get("begin"); + double end = segment->Get("end"); - if (begin < now) + Log(LogDebug, "ScheduledDowntime") + << "Considering segment: " << Utility::FormatDateTime("%c", begin) << " -> " << Utility::FormatDateTime("%c", end); + + if (begin < now) { + Log(LogDebug, "ScheduledDowntime") << "already running."; + continue; + } + if (minBegin && begin < minBegin) { + Log(LogDebug, "ScheduledDowntime") << "beginning to early."; 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() { + /* Try to merge the next segment into a running downtime */ + Log(LogDebug, "ScheduledDowntime") << "Try merge"; for (const Downtime::Ptr& downtime : GetCheckable()->GetDowntimes()) { + double current_end; + if (downtime->GetScheduledBy() != GetName()) { + Log(LogDebug, "ScheduledDowntime") << "Not by us (" << downtime->GetScheduledBy() << " != " << GetName() << ")"; + continue; + } + current_end = downtime->GetEndTime(); + if (current_end > Utility::GetTime() + 12*60*60) { + Log(LogDebug, "ScheduledDowntime") << "By us, long-running (" << Utility::FormatDateTime("%c", current_end) << ")"; + /* return anyway, don't queue a new downtime now */ + return; + } else { + Log(LogDebug, "ScheduledDowntime") << "By us, ends soon (" << Utility::FormatDateTime("%c", current_end) << ")"; + std::pair segment = FindNextSegment(current_end); + /* Merge an immediately following segment */ + if (segment.first == current_end) { + Log(LogDebug, "ScheduledDowntime") << "Next Segment fits, extending end time " << Utility::FormatDateTime("%c", current_end) << " to " << Utility::FormatDateTime("%c", segment.second); + downtime->SetEndTime(segment.second, false); + return; + } else { + Log(LogDebug, "ScheduledDowntime") << "Next Segment doesn't fit: " << Utility::FormatDateTime("%c", segment.first) << " != " << Utility::FormatDateTime("%c", current_end); + continue; + } + } + } + Log(LogDebug, "ScheduledDowntime") << "No merge"; + + 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 +287,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(), diff --git a/lib/icinga/scheduleddowntime.hpp b/lib/icinga/scheduleddowntime.hpp index 7fbcd1e126b..63ce504b2a7 100644 --- a/lib/icinga/scheduleddowntime.hpp +++ b/lib/icinga/scheduleddowntime.hpp @@ -58,7 +58,8 @@ class ScheduledDowntime final : public ObjectImpl private: static void TimerProc(); - std::pair FindNextSegment(); + std::pair FindRunningSegment(double minEnd = 0); + std::pair FindNextSegment(double minBegin = 0); void CreateNextDowntime(); static bool EvaluateApplyRuleInstance(const Checkable::Ptr& checkable, const String& name, ScriptFrame& frame, const ApplyRule& rule); From 406e5f254adf6dcacfc7b2be6d50714e8dff561e Mon Sep 17 00:00:00 2001 From: efuss Date: Tue, 18 Sep 2018 10:22:57 +0200 Subject: [PATCH 2/7] Fix minEnd handling in FindRunningSegment() In ScheduledDowntime::FindRunningSegment(), only regard downtimes that last longer than minEnd, not at least as long. Otherwise, a running downtime with a fixed start date will be queued over and over again. --- lib/icinga/scheduleddowntime.cpp | 42 +++----------------------------- 1 file changed, 3 insertions(+), 39 deletions(-) diff --git a/lib/icinga/scheduleddowntime.cpp b/lib/icinga/scheduleddowntime.cpp index fa6f69cf939..f95a80709fa 100644 --- a/lib/icinga/scheduleddowntime.cpp +++ b/lib/icinga/scheduleddowntime.cpp @@ -138,7 +138,7 @@ std::pair ScheduledDowntime::FindRunningSegment(double minEnd) ObjectLock olock(ranges); - /* Find the longest lasting (at least until minEnd, if given) segment that's already running */ + /* 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; @@ -158,7 +158,7 @@ std::pair ScheduledDowntime::FindRunningSegment(double minEnd) Log(LogDebug, "ScheduledDowntime") << "not running."; continue; } - if (minEnd && end < minEnd) { + if (minEnd && end <= minEnd) { Log(LogDebug, "ScheduledDowntime") << "ending too early."; continue; } @@ -294,35 +294,9 @@ void ScheduledDowntime::CreateNextDowntime() return; } - String downtimeName = Downtime::AddDowntime(GetCheckable(), GetAuthor(), GetComment(), + Downtime::AddDowntime(GetCheckable(), GetAuthor(), GetComment(), segment.first, segment.second, GetFixed(), String(), GetDuration(), GetName(), GetName()); - - Downtime::Ptr downtime = Downtime::GetByName(downtimeName); - - int childOptions = Downtime::ChildOptionsFromValue(GetChildOptions()); - if (childOptions > 0) { - /* 'DowntimeTriggeredChildren' schedules child downtimes triggered by the parent downtime. - * 'DowntimeNonTriggeredChildren' schedules non-triggered downtimes for all children. - */ - String triggerName; - if (childOptions == 1) - triggerName = downtimeName; - - Log(LogNotice, "ScheduledDowntime") - << "Processing child options " << childOptions << " for downtime " << downtimeName; - - for (const Checkable::Ptr& child : GetCheckable()->GetAllChildren()) { - Log(LogNotice, "ScheduledDowntime") - << "Scheduling downtime for child object " << child->GetName(); - - String childDowntimeName = Downtime::AddDowntime(child, GetAuthor(), GetComment(), - segment.first, segment.second, GetFixed(), triggerName, GetDuration(), GetName(), GetName()); - - Log(LogNotice, "ScheduledDowntime") - << "Add child downtime '" << childDowntimeName << "'."; - } - } } void ScheduledDowntime::ValidateRanges(const Lazy& lvalue, const ValidationUtils& utils) @@ -355,13 +329,3 @@ void ScheduledDowntime::ValidateRanges(const Lazy& lvalue, cons } } -void ScheduledDowntime::ValidateChildOptions(const Lazy& lvalue, const ValidationUtils& utils) -{ - ObjectImpl::ValidateChildOptions(lvalue, utils); - - try { - Downtime::ChildOptionsFromValue(lvalue()); - } catch (const std::exception&) { - BOOST_THROW_EXCEPTION(ValidationError(this, { "child_options" }, "Invalid child_options specified")); - } -} \ No newline at end of file From 201f54788ae15e242e14312c612589a7532b929e Mon Sep 17 00:00:00 2001 From: efuss Date: Tue, 18 Sep 2018 10:45:39 +0200 Subject: [PATCH 3/7] Revert 406e5f254adf6dcacfc7b2be6d50714e8dff561e Revert commit 406e5f254adf6dcacfc7b2be6d50714e8dff561e as the patched file was from the wrong branch --- lib/icinga/scheduleddowntime.cpp | 42 +++++++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/lib/icinga/scheduleddowntime.cpp b/lib/icinga/scheduleddowntime.cpp index f95a80709fa..fa6f69cf939 100644 --- a/lib/icinga/scheduleddowntime.cpp +++ b/lib/icinga/scheduleddowntime.cpp @@ -138,7 +138,7 @@ std::pair ScheduledDowntime::FindRunningSegment(double minEnd) ObjectLock olock(ranges); - /* Find the longest lasting (and longer than minEnd, if given) segment that's already running */ + /* Find the longest lasting (at least until minEnd, if given) segment that's already running */ for (const Dictionary::Pair& kv : ranges) { Log(LogDebug, "ScheduledDowntime") << "Evaluating (running?) segment: " << kv.first << ": " << kv.second; @@ -158,7 +158,7 @@ std::pair ScheduledDowntime::FindRunningSegment(double minEnd) Log(LogDebug, "ScheduledDowntime") << "not running."; continue; } - if (minEnd && end <= minEnd) { + if (minEnd && end < minEnd) { Log(LogDebug, "ScheduledDowntime") << "ending too early."; continue; } @@ -294,9 +294,35 @@ void ScheduledDowntime::CreateNextDowntime() return; } - Downtime::AddDowntime(GetCheckable(), GetAuthor(), GetComment(), + String downtimeName = Downtime::AddDowntime(GetCheckable(), GetAuthor(), GetComment(), segment.first, segment.second, GetFixed(), String(), GetDuration(), GetName(), GetName()); + + Downtime::Ptr downtime = Downtime::GetByName(downtimeName); + + int childOptions = Downtime::ChildOptionsFromValue(GetChildOptions()); + if (childOptions > 0) { + /* 'DowntimeTriggeredChildren' schedules child downtimes triggered by the parent downtime. + * 'DowntimeNonTriggeredChildren' schedules non-triggered downtimes for all children. + */ + String triggerName; + if (childOptions == 1) + triggerName = downtimeName; + + Log(LogNotice, "ScheduledDowntime") + << "Processing child options " << childOptions << " for downtime " << downtimeName; + + for (const Checkable::Ptr& child : GetCheckable()->GetAllChildren()) { + Log(LogNotice, "ScheduledDowntime") + << "Scheduling downtime for child object " << child->GetName(); + + String childDowntimeName = Downtime::AddDowntime(child, GetAuthor(), GetComment(), + segment.first, segment.second, GetFixed(), triggerName, GetDuration(), GetName(), GetName()); + + Log(LogNotice, "ScheduledDowntime") + << "Add child downtime '" << childDowntimeName << "'."; + } + } } void ScheduledDowntime::ValidateRanges(const Lazy& lvalue, const ValidationUtils& utils) @@ -329,3 +355,13 @@ void ScheduledDowntime::ValidateRanges(const Lazy& lvalue, cons } } +void ScheduledDowntime::ValidateChildOptions(const Lazy& lvalue, const ValidationUtils& utils) +{ + ObjectImpl::ValidateChildOptions(lvalue, utils); + + try { + Downtime::ChildOptionsFromValue(lvalue()); + } catch (const std::exception&) { + BOOST_THROW_EXCEPTION(ValidationError(this, { "child_options" }, "Invalid child_options specified")); + } +} \ No newline at end of file From 2e721ba112109e34cddb46c66feb4a4a54a68b68 Mon Sep 17 00:00:00 2001 From: efuss Date: Tue, 18 Sep 2018 10:46:54 +0200 Subject: [PATCH 4/7] Fix minEnd handling in FindRunningSegment() In ScheduledDowntime::FindRunningSegment(), only regard downtimes that last longer than minEnd, not at least as long. Otherwise, a running downtime with a fixed start date will be queued over and over again. --- scheduleddowntime.cpp | 367 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 367 insertions(+) create mode 100644 scheduleddowntime.cpp diff --git a/scheduleddowntime.cpp b/scheduleddowntime.cpp new file mode 100644 index 00000000000..0764e629c7e --- /dev/null +++ b/scheduleddowntime.cpp @@ -0,0 +1,367 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2018 Icinga Development Team (https://www.icinga.com/) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#include "icinga/scheduleddowntime.hpp" +#include "icinga/scheduleddowntime-ti.cpp" +#include "icinga/legacytimeperiod.hpp" +#include "icinga/downtime.hpp" +#include "icinga/service.hpp" +#include "base/timer.hpp" +#include "base/configtype.hpp" +#include "base/utility.hpp" +#include "base/objectlock.hpp" +#include "base/convert.hpp" +#include "base/logger.hpp" +#include "base/exception.hpp" +#include + +using namespace icinga; + +REGISTER_TYPE(ScheduledDowntime); + +static Timer::Ptr l_Timer; + +String ScheduledDowntimeNameComposer::MakeName(const String& shortName, const Object::Ptr& context) const +{ + ScheduledDowntime::Ptr downtime = dynamic_pointer_cast(context); + + if (!downtime) + return ""; + + String name = downtime->GetHostName(); + + if (!downtime->GetServiceName().IsEmpty()) + name += "!" + downtime->GetServiceName(); + + name += "!" + shortName; + + return name; +} + +Dictionary::Ptr ScheduledDowntimeNameComposer::ParseName(const String& name) const +{ + std::vector tokens = name.Split("!"); + + if (tokens.size() < 2) + BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid ScheduledDowntime name.")); + + Dictionary::Ptr result = new Dictionary(); + result->Set("host_name", tokens[0]); + + if (tokens.size() > 2) { + result->Set("service_name", tokens[1]); + result->Set("name", tokens[2]); + } else { + result->Set("name", tokens[1]); + } + + return result; +} + +void ScheduledDowntime::OnAllConfigLoaded() +{ + ObjectImpl::OnAllConfigLoaded(); + + if (!GetCheckable()) + BOOST_THROW_EXCEPTION(ScriptError("ScheduledDowntime '" + GetName() + "' references a host/service which doesn't exist.", GetDebugInfo())); +} + +void ScheduledDowntime::Start(bool runtimeCreated) +{ + ObjectImpl::Start(runtimeCreated); + + static boost::once_flag once = BOOST_ONCE_INIT; + + boost::call_once(once, [this]() { + l_Timer = new Timer(); + l_Timer->SetInterval(60); + l_Timer->OnTimerExpired.connect(std::bind(&ScheduledDowntime::TimerProc)); + l_Timer->Start(); + }); + + Utility::QueueAsyncCallback(std::bind(&ScheduledDowntime::CreateNextDowntime, this)); +} + +void ScheduledDowntime::TimerProc() +{ + for (const ScheduledDowntime::Ptr& sd : ConfigType::GetObjectsByType()) { + if (sd->IsActive()) + sd->CreateNextDowntime(); + } +} + +Checkable::Ptr ScheduledDowntime::GetCheckable() const +{ + Host::Ptr host = Host::GetByName(GetHostName()); + + if (GetServiceName().IsEmpty()) + return host; + else + 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(double minBegin) +{ + time_t refts = Utility::GetTime(); + tm reference = Utility::LocalTime(refts); + + Log(LogDebug, "ScheduledDowntime") + << "Finding next scheduled downtime segment for time " << refts + << " (minBegin " << (minBegin > 0 ? Utility::FormatDateTime("%c", minBegin) : "-") << ")"; + + 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 segment starting earliest, but not earlier than minBegin */ + for (const Dictionary::Pair& kv : ranges) { + Log(LogDebug, "ScheduledDowntime") + << "Evaluating segment: " << kv.first << ": " << kv.second; + + Dictionary::Ptr segment = LegacyTimePeriod::FindNextSegment(kv.first, kv.second, &reference); + + if (!segment) + continue; + + 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) { + Log(LogDebug, "ScheduledDowntime") << "already running."; + continue; + } + if (minBegin && begin < minBegin) { + Log(LogDebug, "ScheduledDowntime") << "beginning to early."; + continue; + } + + if (!bestSegment || begin < bestBegin) { + 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); +} + +void ScheduledDowntime::CreateNextDowntime() +{ + /* Try to merge the next segment into a running downtime */ + Log(LogDebug, "ScheduledDowntime") << "Try merge"; + for (const Downtime::Ptr& downtime : GetCheckable()->GetDowntimes()) { + double current_end; + if (downtime->GetScheduledBy() != GetName()) { + Log(LogDebug, "ScheduledDowntime") << "Not by us (" << downtime->GetScheduledBy() << " != " << GetName() << ")"; + continue; + } + current_end = downtime->GetEndTime(); + if (current_end > Utility::GetTime() + 12*60*60) { + Log(LogDebug, "ScheduledDowntime") << "By us, long-running (" << Utility::FormatDateTime("%c", current_end) << ")"; + /* return anyway, don't queue a new downtime now */ + return; + } else { + Log(LogDebug, "ScheduledDowntime") << "By us, ends soon (" << Utility::FormatDateTime("%c", current_end) << ")"; + std::pair segment = FindNextSegment(current_end); + /* Merge an immediately following segment */ + if (segment.first == current_end) { + Log(LogDebug, "ScheduledDowntime") << "Next Segment fits, extending end time " << Utility::FormatDateTime("%c", current_end) << " to " << Utility::FormatDateTime("%c", segment.second); + downtime->SetEndTime(segment.second, false); + return; + } else { + Log(LogDebug, "ScheduledDowntime") << "Next Segment doesn't fit: " << Utility::FormatDateTime("%c", segment.first) << " != " << Utility::FormatDateTime("%c", current_end); + continue; + } + } + } + Log(LogDebug, "ScheduledDowntime") << "No merge"; + + 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; + + /* We've found a downtime that is owned by us and that hasn't started yet - we're done. */ + return; + } + + Log(LogDebug, "ScheduledDowntime") + << "Creating new Downtime for ScheduledDowntime \"" << GetName() << "\""; + + std::pair segment = FindRunningSegment(minEnd); + if (segment.first == 0 && segment.second == 0) { + segment = FindNextSegment(); + if (segment.first == 0 && segment.second == 0) + return; + } + + String downtimeName = Downtime::AddDowntime(GetCheckable(), GetAuthor(), GetComment(), + segment.first, segment.second, + GetFixed(), String(), GetDuration(), GetName(), GetName()); + + Downtime::Ptr downtime = Downtime::GetByName(downtimeName); + + int childOptions = Downtime::ChildOptionsFromValue(GetChildOptions()); + if (childOptions > 0) { + /* 'DowntimeTriggeredChildren' schedules child downtimes triggered by the parent downtime. + * 'DowntimeNonTriggeredChildren' schedules non-triggered downtimes for all children. + */ + String triggerName; + if (childOptions == 1) + triggerName = downtimeName; + + Log(LogNotice, "ScheduledDowntime") + << "Processing child options " << childOptions << " for downtime " << downtimeName; + + for (const Checkable::Ptr& child : GetCheckable()->GetAllChildren()) { + Log(LogNotice, "ScheduledDowntime") + << "Scheduling downtime for child object " << child->GetName(); + + String childDowntimeName = Downtime::AddDowntime(child, GetAuthor(), GetComment(), + segment.first, segment.second, GetFixed(), triggerName, GetDuration(), GetName(), GetName()); + + Log(LogNotice, "ScheduledDowntime") + << "Add child downtime '" << childDowntimeName << "'."; + } + } +} + +void ScheduledDowntime::ValidateRanges(const Lazy& lvalue, const ValidationUtils& utils) +{ + ObjectImpl::ValidateRanges(lvalue, utils); + + if (!lvalue()) + return; + + /* create a fake time environment to validate the definitions */ + time_t refts = Utility::GetTime(); + tm reference = Utility::LocalTime(refts); + Array::Ptr segments = new Array(); + + ObjectLock olock(lvalue()); + for (const Dictionary::Pair& kv : lvalue()) { + try { + tm begin_tm, end_tm; + int stride; + LegacyTimePeriod::ParseTimeRange(kv.first, &begin_tm, &end_tm, &stride, &reference); + } catch (const std::exception& ex) { + BOOST_THROW_EXCEPTION(ValidationError(this, { "ranges" }, "Invalid time specification '" + kv.first + "': " + ex.what())); + } + + try { + LegacyTimePeriod::ProcessTimeRanges(kv.second, &reference, segments); + } catch (const std::exception& ex) { + BOOST_THROW_EXCEPTION(ValidationError(this, { "ranges" }, "Invalid time range definition '" + kv.second + "': " + ex.what())); + } + } +} + +void ScheduledDowntime::ValidateChildOptions(const Lazy& lvalue, const ValidationUtils& utils) +{ + ObjectImpl::ValidateChildOptions(lvalue, utils); + + try { + Downtime::ChildOptionsFromValue(lvalue()); + } catch (const std::exception&) { + BOOST_THROW_EXCEPTION(ValidationError(this, { "child_options" }, "Invalid child_options specified")); + } +} From 28b6545b3b2819e8f61e95d16febc72bac48771c Mon Sep 17 00:00:00 2001 From: efuss Date: Tue, 18 Sep 2018 11:06:50 +0200 Subject: [PATCH 5/7] revert 2e721ba112109e34cddb46c66feb4a4a54a68b68 Revert commit 2e721ba112109e34cddb46c66feb4a4a54a68b68 since it put scheduleddowntime.cpp in the wrong place. --- scheduleddowntime.cpp | 367 ------------------------------------------ 1 file changed, 367 deletions(-) delete mode 100644 scheduleddowntime.cpp diff --git a/scheduleddowntime.cpp b/scheduleddowntime.cpp deleted file mode 100644 index 0764e629c7e..00000000000 --- a/scheduleddowntime.cpp +++ /dev/null @@ -1,367 +0,0 @@ -/****************************************************************************** - * Icinga 2 * - * Copyright (C) 2012-2018 Icinga Development Team (https://www.icinga.com/) * - * * - * This program is free software; you can redistribute it and/or * - * modify it under the terms of the GNU General Public License * - * as published by the Free Software Foundation; either version 2 * - * of the License, or (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the Free Software Foundation * - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * - ******************************************************************************/ - -#include "icinga/scheduleddowntime.hpp" -#include "icinga/scheduleddowntime-ti.cpp" -#include "icinga/legacytimeperiod.hpp" -#include "icinga/downtime.hpp" -#include "icinga/service.hpp" -#include "base/timer.hpp" -#include "base/configtype.hpp" -#include "base/utility.hpp" -#include "base/objectlock.hpp" -#include "base/convert.hpp" -#include "base/logger.hpp" -#include "base/exception.hpp" -#include - -using namespace icinga; - -REGISTER_TYPE(ScheduledDowntime); - -static Timer::Ptr l_Timer; - -String ScheduledDowntimeNameComposer::MakeName(const String& shortName, const Object::Ptr& context) const -{ - ScheduledDowntime::Ptr downtime = dynamic_pointer_cast(context); - - if (!downtime) - return ""; - - String name = downtime->GetHostName(); - - if (!downtime->GetServiceName().IsEmpty()) - name += "!" + downtime->GetServiceName(); - - name += "!" + shortName; - - return name; -} - -Dictionary::Ptr ScheduledDowntimeNameComposer::ParseName(const String& name) const -{ - std::vector tokens = name.Split("!"); - - if (tokens.size() < 2) - BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid ScheduledDowntime name.")); - - Dictionary::Ptr result = new Dictionary(); - result->Set("host_name", tokens[0]); - - if (tokens.size() > 2) { - result->Set("service_name", tokens[1]); - result->Set("name", tokens[2]); - } else { - result->Set("name", tokens[1]); - } - - return result; -} - -void ScheduledDowntime::OnAllConfigLoaded() -{ - ObjectImpl::OnAllConfigLoaded(); - - if (!GetCheckable()) - BOOST_THROW_EXCEPTION(ScriptError("ScheduledDowntime '" + GetName() + "' references a host/service which doesn't exist.", GetDebugInfo())); -} - -void ScheduledDowntime::Start(bool runtimeCreated) -{ - ObjectImpl::Start(runtimeCreated); - - static boost::once_flag once = BOOST_ONCE_INIT; - - boost::call_once(once, [this]() { - l_Timer = new Timer(); - l_Timer->SetInterval(60); - l_Timer->OnTimerExpired.connect(std::bind(&ScheduledDowntime::TimerProc)); - l_Timer->Start(); - }); - - Utility::QueueAsyncCallback(std::bind(&ScheduledDowntime::CreateNextDowntime, this)); -} - -void ScheduledDowntime::TimerProc() -{ - for (const ScheduledDowntime::Ptr& sd : ConfigType::GetObjectsByType()) { - if (sd->IsActive()) - sd->CreateNextDowntime(); - } -} - -Checkable::Ptr ScheduledDowntime::GetCheckable() const -{ - Host::Ptr host = Host::GetByName(GetHostName()); - - if (GetServiceName().IsEmpty()) - return host; - else - 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(double minBegin) -{ - time_t refts = Utility::GetTime(); - tm reference = Utility::LocalTime(refts); - - Log(LogDebug, "ScheduledDowntime") - << "Finding next scheduled downtime segment for time " << refts - << " (minBegin " << (minBegin > 0 ? Utility::FormatDateTime("%c", minBegin) : "-") << ")"; - - 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 segment starting earliest, but not earlier than minBegin */ - for (const Dictionary::Pair& kv : ranges) { - Log(LogDebug, "ScheduledDowntime") - << "Evaluating segment: " << kv.first << ": " << kv.second; - - Dictionary::Ptr segment = LegacyTimePeriod::FindNextSegment(kv.first, kv.second, &reference); - - if (!segment) - continue; - - 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) { - Log(LogDebug, "ScheduledDowntime") << "already running."; - continue; - } - if (minBegin && begin < minBegin) { - Log(LogDebug, "ScheduledDowntime") << "beginning to early."; - continue; - } - - if (!bestSegment || begin < bestBegin) { - 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); -} - -void ScheduledDowntime::CreateNextDowntime() -{ - /* Try to merge the next segment into a running downtime */ - Log(LogDebug, "ScheduledDowntime") << "Try merge"; - for (const Downtime::Ptr& downtime : GetCheckable()->GetDowntimes()) { - double current_end; - if (downtime->GetScheduledBy() != GetName()) { - Log(LogDebug, "ScheduledDowntime") << "Not by us (" << downtime->GetScheduledBy() << " != " << GetName() << ")"; - continue; - } - current_end = downtime->GetEndTime(); - if (current_end > Utility::GetTime() + 12*60*60) { - Log(LogDebug, "ScheduledDowntime") << "By us, long-running (" << Utility::FormatDateTime("%c", current_end) << ")"; - /* return anyway, don't queue a new downtime now */ - return; - } else { - Log(LogDebug, "ScheduledDowntime") << "By us, ends soon (" << Utility::FormatDateTime("%c", current_end) << ")"; - std::pair segment = FindNextSegment(current_end); - /* Merge an immediately following segment */ - if (segment.first == current_end) { - Log(LogDebug, "ScheduledDowntime") << "Next Segment fits, extending end time " << Utility::FormatDateTime("%c", current_end) << " to " << Utility::FormatDateTime("%c", segment.second); - downtime->SetEndTime(segment.second, false); - return; - } else { - Log(LogDebug, "ScheduledDowntime") << "Next Segment doesn't fit: " << Utility::FormatDateTime("%c", segment.first) << " != " << Utility::FormatDateTime("%c", current_end); - continue; - } - } - } - Log(LogDebug, "ScheduledDowntime") << "No merge"; - - 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; - - /* We've found a downtime that is owned by us and that hasn't started yet - we're done. */ - return; - } - - Log(LogDebug, "ScheduledDowntime") - << "Creating new Downtime for ScheduledDowntime \"" << GetName() << "\""; - - std::pair segment = FindRunningSegment(minEnd); - if (segment.first == 0 && segment.second == 0) { - segment = FindNextSegment(); - if (segment.first == 0 && segment.second == 0) - return; - } - - String downtimeName = Downtime::AddDowntime(GetCheckable(), GetAuthor(), GetComment(), - segment.first, segment.second, - GetFixed(), String(), GetDuration(), GetName(), GetName()); - - Downtime::Ptr downtime = Downtime::GetByName(downtimeName); - - int childOptions = Downtime::ChildOptionsFromValue(GetChildOptions()); - if (childOptions > 0) { - /* 'DowntimeTriggeredChildren' schedules child downtimes triggered by the parent downtime. - * 'DowntimeNonTriggeredChildren' schedules non-triggered downtimes for all children. - */ - String triggerName; - if (childOptions == 1) - triggerName = downtimeName; - - Log(LogNotice, "ScheduledDowntime") - << "Processing child options " << childOptions << " for downtime " << downtimeName; - - for (const Checkable::Ptr& child : GetCheckable()->GetAllChildren()) { - Log(LogNotice, "ScheduledDowntime") - << "Scheduling downtime for child object " << child->GetName(); - - String childDowntimeName = Downtime::AddDowntime(child, GetAuthor(), GetComment(), - segment.first, segment.second, GetFixed(), triggerName, GetDuration(), GetName(), GetName()); - - Log(LogNotice, "ScheduledDowntime") - << "Add child downtime '" << childDowntimeName << "'."; - } - } -} - -void ScheduledDowntime::ValidateRanges(const Lazy& lvalue, const ValidationUtils& utils) -{ - ObjectImpl::ValidateRanges(lvalue, utils); - - if (!lvalue()) - return; - - /* create a fake time environment to validate the definitions */ - time_t refts = Utility::GetTime(); - tm reference = Utility::LocalTime(refts); - Array::Ptr segments = new Array(); - - ObjectLock olock(lvalue()); - for (const Dictionary::Pair& kv : lvalue()) { - try { - tm begin_tm, end_tm; - int stride; - LegacyTimePeriod::ParseTimeRange(kv.first, &begin_tm, &end_tm, &stride, &reference); - } catch (const std::exception& ex) { - BOOST_THROW_EXCEPTION(ValidationError(this, { "ranges" }, "Invalid time specification '" + kv.first + "': " + ex.what())); - } - - try { - LegacyTimePeriod::ProcessTimeRanges(kv.second, &reference, segments); - } catch (const std::exception& ex) { - BOOST_THROW_EXCEPTION(ValidationError(this, { "ranges" }, "Invalid time range definition '" + kv.second + "': " + ex.what())); - } - } -} - -void ScheduledDowntime::ValidateChildOptions(const Lazy& lvalue, const ValidationUtils& utils) -{ - ObjectImpl::ValidateChildOptions(lvalue, utils); - - try { - Downtime::ChildOptionsFromValue(lvalue()); - } catch (const std::exception&) { - BOOST_THROW_EXCEPTION(ValidationError(this, { "child_options" }, "Invalid child_options specified")); - } -} From f4614995a6a95cf49658c24999cfdf38364380e1 Mon Sep 17 00:00:00 2001 From: efuss Date: Tue, 18 Sep 2018 11:09:14 +0200 Subject: [PATCH 6/7] Fix minEnd handling in FindRunningSegment() In ScheduledDowntime::FindRunningSegment(), only regard downtimes that last longer than minEnd, not at least as long. Otherwise, a running downtime with a fixed start date will be queued over and over again. --- lib/icinga/scheduleddowntime.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/icinga/scheduleddowntime.cpp b/lib/icinga/scheduleddowntime.cpp index fa6f69cf939..0764e629c7e 100644 --- a/lib/icinga/scheduleddowntime.cpp +++ b/lib/icinga/scheduleddowntime.cpp @@ -138,7 +138,7 @@ std::pair ScheduledDowntime::FindRunningSegment(double minEnd) ObjectLock olock(ranges); - /* Find the longest lasting (at least until minEnd, if given) segment that's already running */ + /* 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; @@ -158,7 +158,7 @@ std::pair ScheduledDowntime::FindRunningSegment(double minEnd) Log(LogDebug, "ScheduledDowntime") << "not running."; continue; } - if (minEnd && end < minEnd) { + if (minEnd && end <= minEnd) { Log(LogDebug, "ScheduledDowntime") << "ending too early."; continue; } @@ -364,4 +364,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 +} From 1e5821466903a500170c2d9d4be39c3876e5411b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Fu=C3=9F?= Date: Thu, 18 Oct 2018 17:46:48 +0200 Subject: [PATCH 7/7] Surpress misleading debug message Surpress a mislading debug message stating the next segment won't fit because its start time (The Epoch) didn't match. Instead, log that no next segment exists. --- lib/icinga/scheduleddowntime.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/icinga/scheduleddowntime.cpp b/lib/icinga/scheduleddowntime.cpp index 0764e629c7e..ea370f7d699 100644 --- a/lib/icinga/scheduleddowntime.cpp +++ b/lib/icinga/scheduleddowntime.cpp @@ -257,7 +257,9 @@ void ScheduledDowntime::CreateNextDowntime() Log(LogDebug, "ScheduledDowntime") << "By us, ends soon (" << Utility::FormatDateTime("%c", current_end) << ")"; std::pair segment = FindNextSegment(current_end); /* Merge an immediately following segment */ - if (segment.first == current_end) { + if (segment.first == 0) { + Log(LogDebug, "ScheduledDowntime") << "No next Segment"; + } else if (segment.first == current_end) { Log(LogDebug, "ScheduledDowntime") << "Next Segment fits, extending end time " << Utility::FormatDateTime("%c", current_end) << " to " << Utility::FormatDateTime("%c", segment.second); downtime->SetEndTime(segment.second, false); return; @@ -364,4 +366,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