From 41ded2858c70d4f76acf1072c474b7e10004804c Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Wed, 13 Nov 2013 14:56:31 +0100 Subject: [PATCH] Implement scheduled downtimes. Fixes #3584 --- components/cluster/clusterlistener.cpp | 3 +- doc/4.3-object-types.md | 38 +++++ etc/CMakeLists.txt | 1 + etc/icinga2/conf.d/downtimes.conf | 19 +++ etc/icinga2/conf.d/localhost.conf | 6 +- lib/base/dynamicobject.cpp | 2 + lib/base/utility.cpp | 25 ++++ lib/base/utility.h | 2 + lib/config/configitem.cpp | 4 + lib/db_ido/CMakeLists.txt | 2 +- lib/db_ido/timeperioddbobject.cpp | 22 +-- lib/icinga/CMakeLists.txt | 8 +- lib/icinga/downtime.ti | 1 + lib/icinga/icinga-type.conf | 35 +++++ lib/{methods => icinga}/legacytimeperiod.cpp | 93 +++++++++--- lib/{methods => icinga}/legacytimeperiod.h | 5 +- lib/icinga/scheduleddowntime.cpp | 146 +++++++++++++++++++ lib/icinga/scheduleddowntime.h | 58 ++++++++ lib/icinga/scheduleddowntime.ti | 22 +++ lib/icinga/service-comment.cpp | 2 + lib/icinga/service-downtime.cpp | 79 +++++++++- lib/icinga/service.cpp | 29 +++- lib/icinga/service.h | 6 +- lib/icinga/service.ti | 1 + lib/methods/CMakeLists.txt | 6 +- 25 files changed, 550 insertions(+), 65 deletions(-) create mode 100644 etc/icinga2/conf.d/downtimes.conf rename lib/{methods => icinga}/legacytimeperiod.cpp (87%) rename lib/{methods => icinga}/legacytimeperiod.h (93%) create mode 100644 lib/icinga/scheduleddowntime.cpp create mode 100644 lib/icinga/scheduleddowntime.h create mode 100644 lib/icinga/scheduleddowntime.ti diff --git a/components/cluster/clusterlistener.cpp b/components/cluster/clusterlistener.cpp index 5cf0b456a..a56ff286c 100644 --- a/components/cluster/clusterlistener.cpp +++ b/components/cluster/clusterlistener.cpp @@ -1272,7 +1272,8 @@ void ClusterListener::MessageHandler(const Endpoint::Ptr& sender, const Dictiona service->AddDowntime(downtime->GetAuthor(), downtime->GetComment(), downtime->GetStartTime(), downtime->GetEndTime(), downtime->GetFixed(), downtime->GetTriggeredBy(), - downtime->GetDuration(), downtime->GetId(), sender->GetName()); + downtime->GetDuration(), downtime->GetScheduledBy(), + downtime->GetId(), sender->GetName()); AsyncRelayMessage(sender, message, true); } else if (message->Get("method") == "cluster::RemoveDowntime") { diff --git a/doc/4.3-object-types.md b/doc/4.3-object-types.md index cc9641d2d..9754735ac 100644 --- a/doc/4.3-object-types.md +++ b/doc/4.3-object-types.md @@ -311,6 +311,44 @@ Attributes: The `/etc/icinga2/conf.d/timeperiods.conf` file is usually used to define timeperiods including this one. +### ScheduledDowntime + +ScheduledDowntime objects can be used to set up recurring downtimes for services. + +> **Best Practice** +> +> Rather than creating a `ScheduledDowntime` object for a specific service it is usually easier +> to just create a `ScheduledDowntime` template and using the `scheduled_downtimes` attribute in the `Service` +> object to associate these templates with a service. + +Example: + + object ScheduledDowntime "some-downtime" { + host = "localhost", + service = "ping4", + + author = "icingaadmin", + comment = "Some comment", + + fixed = false, + duration = 30m, + + ranges = { + "sunday" = "02:00-03:00" + } + } + +Attributes: + + Name |Description + ----------------|---------------- + host |**Required.** The name of the host this notification belongs to. + service |**Required.** The short name of the service this notification belongs to. + author |**Required.** The author of the downtime. + comment |**Required.** A comment for the downtime. + fixed |**Optional.** Whether this is a fixed downtime. Defaults to true. + duration |**Optional.** How long the downtime lasts. Only has an effect for flexible (non-fixed) downtimes. + ranges |**Required.** A dictionary containing information which days and durations apply to this timeperiod. ### ConsoleLogger diff --git a/etc/CMakeLists.txt b/etc/CMakeLists.txt index 99a3bba62..11427fd24 100644 --- a/etc/CMakeLists.txt +++ b/etc/CMakeLists.txt @@ -20,6 +20,7 @@ include(InstallConfig) configure_file(icinga/icinga-classic-apache.conf.cmake ${CMAKE_CURRENT_BINARY_DIR}/icinga/icinga-classic-apache.conf) install_if_not_exists(icinga2/icinga2.conf ${CMAKE_INSTALL_SYSCONFDIR}/icinga2) +install_if_not_exists(icinga2/conf.d/downtimes.conf ${CMAKE_INSTALL_SYSCONFDIR}/icinga2/conf.d) install_if_not_exists(icinga2/conf.d/generic-host.conf ${CMAKE_INSTALL_SYSCONFDIR}/icinga2/conf.d) install_if_not_exists(icinga2/conf.d/generic-service.conf ${CMAKE_INSTALL_SYSCONFDIR}/icinga2/conf.d) install_if_not_exists(icinga2/conf.d/generic-user.conf ${CMAKE_INSTALL_SYSCONFDIR}/icinga2/conf.d) diff --git a/etc/icinga2/conf.d/downtimes.conf b/etc/icinga2/conf.d/downtimes.conf new file mode 100644 index 000000000..f06abc4e7 --- /dev/null +++ b/etc/icinga2/conf.d/downtimes.conf @@ -0,0 +1,19 @@ +/** + * The example downtime templates. + */ + +template ScheduledDowntime "backup-downtime" { + author = "icingaadmin", + comment = "Scheduled downtime for backup", + + ranges = { + monday = "02:00-03:00", + tuesday = "02:00-03:00", + wednesday = "02:00-03:00", + thursday = "02:00-03:00", + friday = "02:00-03:00", + saturday = "02:00-03:00", + sunday = "02:00-03:00" + } +} + diff --git a/etc/icinga2/conf.d/localhost.conf b/etc/icinga2/conf.d/localhost.conf index a03105957..807011582 100644 --- a/etc/icinga2/conf.d/localhost.conf +++ b/etc/icinga2/conf.d/localhost.conf @@ -39,7 +39,11 @@ object Host "localhost" inherits "linux-server" { services["load"] = { templates = [ "generic-service" ], - check_command = "load" + check_command = "load", + + scheduled_downtimes["backup"] = { + templates = [ "backup-downtime" ] + } }, services["processes"] = { diff --git a/lib/base/dynamicobject.cpp b/lib/base/dynamicobject.cpp index 4982f9248..5f637ece9 100644 --- a/lib/base/dynamicobject.cpp +++ b/lib/base/dynamicobject.cpp @@ -278,7 +278,9 @@ void DynamicObject::RestoreObjects(const String& filename, int attributeTypes) if (object) { ASSERT(!object->IsActive()); +#ifdef _DEBUG Log(LogDebug, "base", "Restoring object '" + name + "' of type '" + type + "'."); +#endif /* _DEBUG */ Deserialize(object, update, attributeTypes); object->OnStateLoaded(); } diff --git a/lib/base/utility.cpp b/lib/base/utility.cpp index 0c772c0d0..6e8bdd1f3 100644 --- a/lib/base/utility.cpp +++ b/lib/base/utility.cpp @@ -744,3 +744,28 @@ int Utility::Random(void) return rand(); } + +tm Utility::LocalTime(time_t ts) +{ +#ifdef _MSC_VER + tm *result = localtime(&ts); + + if (temp == NULL) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("localtime") + << boost::errinfo_errno(errno)); + } + + return *result; +#else /* _MSC_VER */ + tm result; + + if (localtime_r(&ts, &result) == NULL) { + BOOST_THROW_EXCEPTION(posix_error() + << boost::errinfo_api_function("localtime_r") + << boost::errinfo_errno(errno)); + } + + return result; +#endif /* _MSC_VER */ +} \ No newline at end of file diff --git a/lib/base/utility.h b/lib/base/utility.h index ba070a6af..07e56edec 100644 --- a/lib/base/utility.h +++ b/lib/base/utility.h @@ -108,6 +108,8 @@ public: static int Random(void); + static tm LocalTime(time_t ts); + private: Utility(void); diff --git a/lib/config/configitem.cpp b/lib/config/configitem.cpp index b7287331b..26720749d 100644 --- a/lib/config/configitem.cpp +++ b/lib/config/configitem.cpp @@ -157,7 +157,9 @@ DynamicObject::Ptr ConfigItem::Commit(void) { ASSERT(!OwnsLock()); +#ifdef _DEBUG Log(LogDebug, "base", "Commit called for ConfigItem Type=" + GetType() + ", Name=" + GetName()); +#endif /* _DEBUG */ /* Make sure the type is valid. */ DynamicType::Ptr dtype = DynamicType::GetByName(GetType()); @@ -284,7 +286,9 @@ bool ConfigItem::ActivateItems(bool validateOnly) if (object->IsActive()) continue; +#ifdef _DEBUG Log(LogDebug, "config", "Activating object '" + object->GetName() + "' of type '" + object->GetType()->GetName() + "'"); +#endif /* _DEBUG */ object->Start(); ASSERT(object->IsActive()); diff --git a/lib/db_ido/CMakeLists.txt b/lib/db_ido/CMakeLists.txt index ae3620ff2..1eb75719a 100644 --- a/lib/db_ido/CMakeLists.txt +++ b/lib/db_ido/CMakeLists.txt @@ -28,7 +28,7 @@ add_library(db_ido SHARED ) include_directories(${Boost_INCLUDE_DIRS}) -target_link_libraries(db_ido ${Boost_LIBRARIES} base config icinga methods) +target_link_libraries(db_ido ${Boost_LIBRARIES} base config icinga) set_target_properties ( db_ido PROPERTIES diff --git a/lib/db_ido/timeperioddbobject.cpp b/lib/db_ido/timeperioddbobject.cpp index 6aab46775..2f1a93cc0 100644 --- a/lib/db_ido/timeperioddbobject.cpp +++ b/lib/db_ido/timeperioddbobject.cpp @@ -21,7 +21,7 @@ #include "db_ido/dbtype.h" #include "db_ido/dbvalue.h" #include "icinga/timeperiod.h" -#include "methods/legacytimeperiod.h" +#include "icinga/legacytimeperiod.h" #include "base/utility.h" #include "base/exception.h" #include "base/objectlock.h" @@ -75,25 +75,7 @@ void TimePeriodDbObject::OnConfigUpdate(void) if (wday == -1) continue; - tm reference; - -#ifdef _MSC_VER - tm *temp = localtime(&refts); - - if (temp == NULL) { - BOOST_THROW_EXCEPTION(posix_error() - << boost::errinfo_api_function("localtime") - << boost::errinfo_errno(errno)); - } - - reference = *temp; -#else /* _MSC_VER */ - if (localtime_r(&refts, &reference) == NULL) { - BOOST_THROW_EXCEPTION(posix_error() - << boost::errinfo_api_function("localtime_r") - << boost::errinfo_errno(errno)); - } -#endif /* _MSC_VER */ + tm reference = Utility::LocalTime(refts); Array::Ptr segments = make_shared(); LegacyTimePeriod::ProcessTimeRanges(kv.second, &reference, segments); diff --git a/lib/icinga/CMakeLists.txt b/lib/icinga/CMakeLists.txt index 853743d32..eea9589f1 100644 --- a/lib/icinga/CMakeLists.txt +++ b/lib/icinga/CMakeLists.txt @@ -28,6 +28,7 @@ mkclass_target(icingaapplication.ti icingaapplication.th) mkclass_target(notificationcommand.ti notificationcommand.th) mkclass_target(notification.ti notification.th) mkclass_target(perfdatavalue.ti perfdatavalue.th) +mkclass_target(scheduleddowntime.ti scheduleddowntime.th) mkclass_target(servicegroup.ti servicegroup.th) mkclass_target(service.ti service.th) mkclass_target(timeperiod.ti timeperiod.th) @@ -41,9 +42,10 @@ add_library(icinga SHARED cib.cpp command.cpp command.th comment.cpp comment.th compatutility.cpp domain.cpp domain.th downtime.cpp downtime.th eventcommand.cpp eventcommand.th externalcommandprocessor.cpp host.cpp host.th hostgroup.cpp hostgroup.th - icingaapplication.cpp icingaapplication.th macroprocessor.cpp macroresolver.cpp - notificationcommand.cpp notificationcommand.th notification.cpp notification.th - perfdatavalue.cpp perfdatavalue.th pluginutility.cpp service-check.cpp + icingaapplication.cpp icingaapplication.th legacytimeperiod.cpp + macroprocessor.cpp macroresolver.cpp notificationcommand.cpp notificationcommand.th + notification.cpp notification.th perfdatavalue.cpp perfdatavalue.th + pluginutility.cpp scheduleddowntime.cpp scheduleddowntime.th service-check.cpp service-comment.cpp service.cpp service-downtime.cpp service-event.cpp service-flapping.cpp service.th servicegroup.cpp servicegroup.th service-notification.cpp timeperiod.cpp timeperiod.th user.cpp user.th diff --git a/lib/icinga/downtime.ti b/lib/icinga/downtime.ti index e26014a58..c68812aea 100644 --- a/lib/icinga/downtime.ti +++ b/lib/icinga/downtime.ti @@ -13,6 +13,7 @@ class Downtime [state] bool fixed; [state] double duration; [state] String triggered_by; + [state] String scheduled_by; [state] Dictionary::Ptr triggers { default {{{ return make_shared(); }}} }; diff --git a/lib/icinga/icinga-type.conf b/lib/icinga/icinga-type.conf index 706b2d7cf..fa8e2e460 100644 --- a/lib/icinga/icinga-type.conf +++ b/lib/icinga/icinga-type.conf @@ -116,6 +116,16 @@ type Service { } }, + %attribute dictionary "scheduled_downtimes" { + %attribute dictionary "*" { + %attribute array "templates" { + %attribute name(ScheduledDowntime) "*" + }, + + %attribute any "*" + } + }, + %attribute any "templates" } @@ -126,6 +136,8 @@ type ServiceGroup { type Notification { %require "host", %attribute name(Host) "host", + + %require "service", %attribute string "service", %attribute dictionary "macros" { @@ -237,3 +249,26 @@ type Domain { %attribute number "*" } } + +type ScheduledDowntime { + %require "host", + %attribute name(Host) "host", + %require "service", + %attribute string "service", + + %require "author", + %attribute string "author", + + %require "comment", + %attribute string "comment", + + %attribute number "duration", + %attribute number "fixed", + + %require "ranges", + %attribute dictionary "ranges" { + %attribute string "*" + }, + + %attribute any "templates" +} diff --git a/lib/methods/legacytimeperiod.cpp b/lib/icinga/legacytimeperiod.cpp similarity index 87% rename from lib/methods/legacytimeperiod.cpp rename to lib/icinga/legacytimeperiod.cpp index fd56d31dc..d49008067 100644 --- a/lib/methods/legacytimeperiod.cpp +++ b/lib/icinga/legacytimeperiod.cpp @@ -17,13 +17,14 @@ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * ******************************************************************************/ -#include "methods/legacytimeperiod.h" +#include "icinga/legacytimeperiod.h" #include "base/scriptfunction.h" #include "base/convert.h" #include "base/exception.h" #include "base/objectlock.h" #include "base/logger_fwd.h" #include "base/debug.h" +#include "base/utility.h" #include #include #include @@ -141,12 +142,12 @@ void LegacyTimePeriod::ParseTimeSpec(const String& timespec, tm *begin, tm *end, if (timespec.GetLength() == 10 && timespec[4] == '-' && timespec[7] == '-') { int year = Convert::ToLong(timespec.SubStr(0, 4)); int month = Convert::ToLong(timespec.SubStr(5, 2)); - int day = Convert::ToLong(timespec.SubStr(7, 2)); + int day = Convert::ToLong(timespec.SubStr(8, 2)); if (begin) { begin->tm_year = year - 1900; begin->tm_mon = month; - begin->tm_mday = day; + begin->tm_mday = day + 1; begin->tm_hour = 0; begin->tm_min = 0; begin->tm_sec = 0; @@ -155,7 +156,7 @@ void LegacyTimePeriod::ParseTimeSpec(const String& timespec, tm *begin, tm *end, if (end) { end->tm_year = year - 1900; end->tm_mon = month; - end->tm_mday = day; + end->tm_mday = day + 1; end->tm_hour = 24; end->tm_min = 0; end->tm_sec = 0; @@ -380,6 +381,64 @@ void LegacyTimePeriod::ProcessTimeRanges(const String& timeranges, tm *reference } } +Dictionary::Ptr LegacyTimePeriod::FindNextSegment(const String& daydef, const String& timeranges, tm *reference) +{ + tm begin, end, iter, ref; + time_t tsend, tsiter, tsref; + int stride; + + for (int pass = 1; pass <= 2; pass++) { + if (pass == 1) { + ref = *reference; + } else { + ref = end; + ref.tm_mday++; + } + + tsref = mktime(&ref); + + ParseTimeRange(daydef, &begin, &end, &stride, &ref); + + iter = begin; + + tsend = mktime(&end); + tsiter = mktime(&iter); + + do { + if (IsInTimeRange(&begin, &end, stride, &iter)) { + Array::Ptr segments = make_shared(); + ProcessTimeRanges(timeranges, &iter, segments); + + Dictionary::Ptr bestSegment; + double bestBegin; + + BOOST_FOREACH(const Dictionary::Ptr& segment, segments) { + double begin = segment->Get("begin"); + + if (begin < tsref) + continue; + + if (!bestSegment || begin < bestBegin) { + bestSegment = segment; + bestBegin = begin; + } + } + + 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 Dictionary::Ptr(); +} + Array::Ptr LegacyTimePeriod::ScriptFunc(const TimePeriod::Ptr& tp, double begin, double end) { Array::Ptr segments = make_shared(); @@ -389,36 +448,24 @@ Array::Ptr LegacyTimePeriod::ScriptFunc(const TimePeriod::Ptr& tp, double begin, if (ranges) { for (int i = 0; i <= (end - begin) / (24 * 60 * 60); i++) { time_t refts = begin + i * 24 * 60 * 60; - tm reference; + tm reference = Utility::LocalTime(refts); +#ifdef _DEBUG Log(LogDebug, "icinga", "Checking reference time " + Convert::ToString(static_cast(refts))); - -#ifdef _MSC_VER - tm *temp = localtime(&refts); - - if (temp == NULL) { - BOOST_THROW_EXCEPTION(posix_error() - << boost::errinfo_api_function("localtime") - << boost::errinfo_errno(errno)); - } - - reference = *temp; -#else /* _MSC_VER */ - if (localtime_r(&refts, &reference) == NULL) { - BOOST_THROW_EXCEPTION(posix_error() - << boost::errinfo_api_function("localtime_r") - << boost::errinfo_errno(errno)); - } -#endif /* _MSC_VER */ +#endif /* _DEBUG */ ObjectLock olock(ranges); BOOST_FOREACH(const Dictionary::Pair& kv, ranges) { if (!IsInDayDefinition(kv.first, &reference)) { +#ifdef _DEBUG Log(LogDebug, "icinga", "Not in day definition '" + kv.first + "'."); +#endif /* _DEBUG */ continue; } +#ifdef _DEBUG Log(LogDebug, "icinga", "In day definition '" + kv.first + "'."); +#endif /* _DEBUG */ ProcessTimeRanges(kv.second, &reference, segments); } diff --git a/lib/methods/legacytimeperiod.h b/lib/icinga/legacytimeperiod.h similarity index 93% rename from lib/methods/legacytimeperiod.h rename to lib/icinga/legacytimeperiod.h index d2cb2ea06..01852d27e 100644 --- a/lib/methods/legacytimeperiod.h +++ b/lib/icinga/legacytimeperiod.h @@ -20,7 +20,7 @@ #ifndef LEGACYTIMEPERIOD_H #define LEGACYTIMEPERIOD_H -#include "methods/i2-methods.h" +#include "icinga/i2-icinga.h" #include "icinga/service.h" #include "base/dictionary.h" @@ -32,7 +32,7 @@ namespace icinga * * @ingroup icinga */ -class I2_METHODS_API LegacyTimePeriod +class I2_ICINGA_API LegacyTimePeriod { public: static Array::Ptr ScriptFunc(const TimePeriod::Ptr& tp, double start, double end); @@ -47,6 +47,7 @@ public: static void ProcessTimeRangeRaw(const String& timerange, tm *reference, tm *begin, tm *end); 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); private: LegacyTimePeriod(void); diff --git a/lib/icinga/scheduleddowntime.cpp b/lib/icinga/scheduleddowntime.cpp new file mode 100644 index 000000000..7aa4a1c76 --- /dev/null +++ b/lib/icinga/scheduleddowntime.cpp @@ -0,0 +1,146 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2013 Icinga Development Team (http://www.icinga.org/) * + * * + * 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.h" +#include "icinga/legacytimeperiod.h" +#include "base/timer.h" +#include "base/dynamictype.h" +#include "base/initialize.h" +#include "base/utility.h" +#include "base/objectlock.h" +#include "base/convert.h" +#include "base/logger_fwd.h" +#include "base/exception.h" +#include + +using namespace icinga; + +REGISTER_TYPE(ScheduledDowntime); + +INITIALIZE_ONCE(&ScheduledDowntime::StaticInitialize); + +static Timer::Ptr l_Timer; + +void ScheduledDowntime::StaticInitialize(void) +{ + l_Timer = make_shared(); + l_Timer->SetInterval(60); + l_Timer->OnTimerExpired.connect(boost::bind(&ScheduledDowntime::TimerProc)); + l_Timer->Start(); +} + +void ScheduledDowntime::Start(void) +{ + DynamicObject::Start(); + + CreateNextDowntime(); +} + +void ScheduledDowntime::TimerProc(void) +{ + BOOST_FOREACH(const ScheduledDowntime::Ptr& sd, DynamicType::GetObjects()) { + sd->CreateNextDowntime(); + } +} + +Service::Ptr ScheduledDowntime::GetService(void) const +{ + Host::Ptr host = Host::GetByName(GetHostRaw()); + + if (GetServiceRaw().IsEmpty()) + return host->GetCheckService(); + else + return host->GetServiceByShortName(GetServiceRaw()); +} + +std::pair ScheduledDowntime::FindNextSegment(void) +{ + time_t refts = Utility::GetTime(); + tm reference = Utility::LocalTime(refts); + + Log(LogDebug, "icinga", "Finding next scheduled downtime segment for time " + Convert::ToString(static_cast(refts))); + + Dictionary::Ptr ranges = GetRanges(); + + Array::Ptr segments = make_shared(); + + Dictionary::Ptr bestSegment; + double bestBegin; + double now = Utility::GetTime(); + + ObjectLock olock(ranges); + BOOST_FOREACH(const Dictionary::Pair& kv, ranges) { + tm rangeref; + + Dictionary::Ptr segment = LegacyTimePeriod::FindNextSegment(kv.first, kv.second, &reference); + + if (!segment) + continue; + + double begin = segment->Get("begin"); + + if (begin < now) + continue; + + if (!bestSegment || begin < bestBegin) { + bestSegment = segment; + bestBegin = begin; + } + } + + if (bestSegment) + return std::make_pair(bestSegment->Get("begin"), bestSegment->Get("end")); + else + return std::make_pair(0, 0); +} + +void ScheduledDowntime::CreateNextDowntime(void) +{ + Dictionary::Ptr downtimes = GetService()->GetDowntimes(); + + { + ObjectLock dlock(downtimes); + BOOST_FOREACH(const Dictionary::Pair& kv, downtimes) { + Downtime::Ptr downtime = kv.second; + + 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; + } + } + + std::pair segment = FindNextSegment(); + + 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; + } + + GetService()->AddDowntime(GetAuthor(), GetComment(), + segment.first, segment.second, + GetFixed(), String(), GetDuration(), GetName()); +} diff --git a/lib/icinga/scheduleddowntime.h b/lib/icinga/scheduleddowntime.h new file mode 100644 index 000000000..2e6b45b6a --- /dev/null +++ b/lib/icinga/scheduleddowntime.h @@ -0,0 +1,58 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012-2013 Icinga Development Team (http://www.icinga.org/) * + * * + * 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. * + ******************************************************************************/ + +#ifndef SCHEDULEDDOWNTIME_H +#define SCHEDULEDDOWNTIME_H + +#include "icinga/i2-icinga.h" +#include "icinga/scheduleddowntime.th" +#include "icinga/service.h" +#include + +namespace icinga +{ + +/** + * An Icinga scheduled downtime specification. + * + * @ingroup icinga + */ +class I2_ICINGA_API ScheduledDowntime : public ObjectImpl +{ +public: + DECLARE_PTR_TYPEDEFS(ScheduledDowntime); + DECLARE_TYPENAME(ScheduledDowntime); + + static void StaticInitialize(void); + + Service::Ptr GetService(void) const; + +protected: + virtual void Start(void); + +private: + static void TimerProc(void); + + std::pair FindNextSegment(void); + void CreateNextDowntime(void); +}; + +} + +#endif /* SCHEDULEDDOWNTIME_H */ diff --git a/lib/icinga/scheduleddowntime.ti b/lib/icinga/scheduleddowntime.ti new file mode 100644 index 000000000..6b31a5fa1 --- /dev/null +++ b/lib/icinga/scheduleddowntime.ti @@ -0,0 +1,22 @@ +#include "base/dynamicobject.h" + +namespace icinga +{ + +class ScheduledDowntime : DynamicObject +{ + [config, protected] String host (HostRaw); + [config, protected] String service (ServiceRaw); + + [config] String author; + [config] String comment; + + [config] double duration; + [config] bool fixed { + default {{{ return true; }}} + }; + + [config] Dictionary::Ptr ranges; +}; + +} diff --git a/lib/icinga/service-comment.cpp b/lib/icinga/service-comment.cpp index bfeae0a87..961a47eb7 100644 --- a/lib/icinga/service-comment.cpp +++ b/lib/icinga/service-comment.cpp @@ -163,7 +163,9 @@ Comment::Ptr Service::GetCommentByID(const String& id) void Service::AddCommentsToCache(void) { +#ifdef _DEBUG Log(LogDebug, "icinga", "Updating Service comments cache."); +#endif /* _DEBUG */ Dictionary::Ptr comments = GetComments(); diff --git a/lib/icinga/service-downtime.cpp b/lib/icinga/service-downtime.cpp index 3a397a827..83b45fc43 100644 --- a/lib/icinga/service-downtime.cpp +++ b/lib/icinga/service-downtime.cpp @@ -18,6 +18,7 @@ ******************************************************************************/ #include "icinga/service.h" +#include "config/configitembuilder.h" #include "base/dynamictype.h" #include "base/objectlock.h" #include "base/logger_fwd.h" @@ -47,7 +48,8 @@ int Service::GetNextDowntimeID(void) String Service::AddDowntime(const String& author, const String& comment, double startTime, double endTime, bool fixed, - const String& triggeredBy, double duration, const String& id, const String& authority) + const String& triggeredBy, double duration, const String& scheduledBy, + const String& id, const String& authority) { String uid; @@ -66,6 +68,7 @@ String Service::AddDowntime(const String& author, const String& comment, downtime->SetFixed(fixed); downtime->SetDuration(duration); downtime->SetTriggeredBy(triggeredBy); + downtime->SetScheduledBy(scheduledBy); int legacy_id; @@ -82,10 +85,7 @@ String Service::AddDowntime(const String& author, const String& comment, Downtime::Ptr otherDowntime = otherDowntimes->Get(triggeredBy); Dictionary::Ptr triggers = otherDowntime->GetTriggers(); - { - ObjectLock olock(otherOwner); - triggers->Set(triggeredBy, triggeredBy); - } + triggers->Set(triggeredBy, triggeredBy); } GetDowntimes()->Set(uid, downtime); @@ -96,7 +96,8 @@ String Service::AddDowntime(const String& author, const String& comment, l_DowntimesCache[uid] = GetSelf(); } - Log(LogWarning, "icinga", "added downtime with ID '" + Convert::ToString(downtime->GetLegacyId()) + "'."); + Log(LogDebug, "icinga", "Added downtime with ID '" + Convert::ToString(downtime->GetLegacyId()) + + "' between '" + Utility::FormatDateTime("%Y-%m-%d %H:%M:%S", startTime) + "' and '" + Utility::FormatDateTime("%Y-%m-%d %H:%M:%S", endTime) + "'."); OnDowntimeAdded(GetSelf(), downtime, authority); @@ -129,7 +130,7 @@ void Service::RemoveDowntime(const String& id, bool cancelled, const String& aut downtime->SetWasCancelled(cancelled); - Log(LogWarning, "icinga", "removed downtime with ID '" + Convert::ToString(downtime->GetLegacyId()) + "' from service '" + owner->GetName() + "'."); + Log(LogDebug, "icinga", "Removed downtime with ID '" + Convert::ToString(downtime->GetLegacyId()) + "' from service '" + owner->GetName() + "'."); OnDowntimeRemoved(owner, downtime, authority); } @@ -230,7 +231,9 @@ void Service::StartDowntimesExpiredTimer(void) void Service::AddDowntimesToCache(void) { +#ifdef _DEBUG Log(LogDebug, "icinga", "Updating Service downtimes cache."); +#endif /* _DEBUG */ Dictionary::Ptr downtimes = GetDowntimes(); @@ -312,3 +315,65 @@ int Service::GetDowntimeDepth(void) const return downtime_depth; } + +void Service::UpdateSlaveScheduledDowntimes(void) +{ + ConfigItem::Ptr item = ConfigItem::GetObject("Service", GetName()); + + /* Don't create slave scheduled downtimes unless we own this object */ + if (!item) + return; + + /* Service scheduled downtime descs */ + Dictionary::Ptr descs = GetScheduledDowntimeDescriptions(); + + if (!descs) + return; + + ObjectLock olock(descs); + + BOOST_FOREACH(const Dictionary::Pair& kv, descs) { + std::ostringstream namebuf; + namebuf << GetName() << ":" << kv.first; + String name = namebuf.str(); + + std::vector path; + path.push_back("scheduled_downtimes"); + path.push_back(kv.first); + + DebugInfo di; + item->GetLinkedExpressionList()->FindDebugInfoPath(path, di); + + if (di.Path.IsEmpty()) + di = item->GetDebugInfo(); + + ConfigItemBuilder::Ptr builder = make_shared(di); + builder->SetType("ScheduledDowntime"); + builder->SetName(name); + builder->AddExpression("host", OperatorSet, GetHost()->GetName()); + builder->AddExpression("service", OperatorSet, GetShortName()); + + Dictionary::Ptr scheduledDowntime = kv.second; + + Array::Ptr templates = scheduledDowntime->Get("templates"); + + if (templates) { + ObjectLock tlock(templates); + + BOOST_FOREACH(const Value& tmpl, templates) { + builder->AddParent(tmpl); + } + } + + /* Clone attributes from the scheduled downtime expression list. */ + ExpressionList::Ptr sd_exprl = make_shared(); + item->GetLinkedExpressionList()->ExtractPath(path, sd_exprl); + + builder->AddExpressionList(sd_exprl); + + ConfigItem::Ptr scheduledDowntimeItem = builder->Compile(); + scheduledDowntimeItem->Register(); + DynamicObject::Ptr dobj = scheduledDowntimeItem->Commit(); + dobj->OnConfigLoaded(); + } +} diff --git a/lib/icinga/service.cpp b/lib/icinga/service.cpp index fcca38ed3..482892758 100644 --- a/lib/icinga/service.cpp +++ b/lib/icinga/service.cpp @@ -46,9 +46,6 @@ void Service::Start(void) { VERIFY(GetHost()); - AddDowntimesToCache(); - AddCommentsToCache(); - StartDowntimesExpiredTimer(); double now = Utility::GetTime(); @@ -80,10 +77,36 @@ void Service::OnConfigLoaded(void) m_Host->AddService(GetSelf()); UpdateSlaveNotifications(); + UpdateSlaveScheduledDowntimes(); SetSchedulingOffset(Utility::Random()); } +void Service::OnStateLoaded(void) +{ + AddDowntimesToCache(); + AddCommentsToCache(); + + std::vector ids; + Dictionary::Ptr downtimes = GetDowntimes(); + + { + ObjectLock dlock(downtimes); + BOOST_FOREACH(const Dictionary::Pair& kv, downtimes) { + Downtime::Ptr downtime = kv.second; + + if (downtime->GetScheduledBy().IsEmpty()) + continue; + + ids.push_back(kv.first); + } + } + + BOOST_FOREACH(const String& id, ids) { + RemoveDowntime(id, true); + } +} + Service::Ptr Service::GetByNamePair(const String& hostName, const String& serviceName) { if (!hostName.IsEmpty()) { diff --git a/lib/icinga/service.h b/lib/icinga/service.h index 1680e2221..c6cd6b8c9 100644 --- a/lib/icinga/service.h +++ b/lib/icinga/service.h @@ -192,7 +192,8 @@ public: String AddDowntime(const String& author, const String& comment, double startTime, double endTime, bool fixed, const String& triggeredBy, double duration, - const String& id = String(), const String& authority = String()); + const String& scheduledBy = String(), const String& id = String(), + const String& authority = String()); static void RemoveDowntime(const String& id, bool cancelled, const String& = String()); @@ -208,6 +209,8 @@ public: bool IsInDowntime(void) const; bool IsAcknowledged(void); + void UpdateSlaveScheduledDowntimes(void); + /* Comments */ static int GetNextCommentID(void); @@ -265,6 +268,7 @@ protected: virtual void Start(void); virtual void OnConfigLoaded(void); + virtual void OnStateLoaded(void); private: Host::Ptr m_Host; diff --git a/lib/icinga/service.ti b/lib/icinga/service.ti index ee693c706..0f91b1307 100644 --- a/lib/icinga/service.ti +++ b/lib/icinga/service.ti @@ -59,6 +59,7 @@ class Service : DynamicObject default {{{ return 30; }}} }; [config] Dictionary::Ptr notifications (NotificationDescriptions); + [config] Dictionary::Ptr scheduled_downtimes (ScheduledDowntimeDescriptions); [config] bool enable_active_checks (EnableActiveChecksRaw) { default {{{ return true; }}} }; diff --git a/lib/methods/CMakeLists.txt b/lib/methods/CMakeLists.txt index 64dde6cbe..745bb33b4 100644 --- a/lib/methods/CMakeLists.txt +++ b/lib/methods/CMakeLists.txt @@ -16,9 +16,9 @@ # Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. add_library(methods SHARED - icingachecktask.cpp legacytimeperiod.cpp nullchecktask.cpp - nulleventtask.cpp pluginchecktask.cpp plugineventtask.cpp - pluginnotificationtask.cpp randomchecktask.cpp timeperiodtask.cpp + icingachecktask.cpp nullchecktask.cpp nulleventtask.cpp + pluginchecktask.cpp plugineventtask.cpp pluginnotificationtask.cpp + randomchecktask.cpp timeperiodtask.cpp ) target_link_libraries(methods ${Boost_LIBRARIES} base config icinga)