From 442a2dbc7d6e6f54554d851b851ecbfc88f0ba56 Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Wed, 13 Mar 2013 16:04:53 +0100 Subject: [PATCH] Implement timeperiods. --- components/checker/checkercomponent.cpp | 21 +- lib/base/dynamictype.h | 2 +- lib/base/scriptfunction.h | 4 +- lib/base/utility.h | 2 + lib/icinga/Makefile.am | 2 + lib/icinga/api.cpp | 2 +- lib/icinga/host.cpp | 6 +- lib/icinga/i2-icinga.h | 4 +- lib/icinga/icinga-type.conf | 11 + lib/icinga/icinga.vcxproj | 4 +- lib/icinga/notification.cpp | 3 +- lib/icinga/nullchecktask.cpp | 2 +- lib/icinga/pluginchecktask.cpp | 2 +- lib/icinga/pluginnotificationtask.cpp | 2 +- lib/icinga/service-check.cpp | 8 + lib/icinga/service.cpp | 1 + lib/icinga/service.h | 2 + lib/icinga/timeperiod.cpp | 280 ++++++++++++++++++++++++ lib/icinga/timeperiod.h | 66 ++++++ 19 files changed, 408 insertions(+), 16 deletions(-) create mode 100644 lib/icinga/timeperiod.cpp create mode 100644 lib/icinga/timeperiod.h diff --git a/components/checker/checkercomponent.cpp b/components/checker/checkercomponent.cpp index f35fcca44..c65ca8925 100644 --- a/components/checker/checkercomponent.cpp +++ b/components/checker/checkercomponent.cpp @@ -98,11 +98,24 @@ void CheckerComponent::CheckThreadProc(void) m_IdleServices.erase(service); - /* reschedule the service if checks are currently disabled - * for it and this is not a forced check */ - if (!service->GetEnableActiveChecks() && !service->GetForceNextCheck()) { - Logger::Write(LogDebug, "checker", "Ignoring service check for disabled service: " + service->GetName()); + bool check = true; + if (!service->GetForceNextCheck()) { + if (!service->GetEnableActiveChecks()) { + Logger::Write(LogDebug, "checker", "Skipping check for service '" + service->GetName() + "': active checks are disabled"); + check = false; + } + + TimePeriod::Ptr tp = service->GetCheckPeriod(); + + if (tp && !tp->IsInside(Utility::GetTime())) { + Logger::Write(LogDebug, "checker", "Skipping check for service '" + service->GetName() + "': not in check_period"); + check = false; + } + } + + /* reschedule the service if checks are disabled */ + if (!check) { service->UpdateNextCheck(); typedef nth_index::type CheckTimeView; diff --git a/lib/base/dynamictype.h b/lib/base/dynamictype.h index 4d10d0fbd..abae27660 100644 --- a/lib/base/dynamictype.h +++ b/lib/base/dynamictype.h @@ -95,7 +95,7 @@ shared_ptr DynamicObjectFactory(const Dictionary::Ptr& serializedUpdate) } #define REGISTER_TYPE_ALIAS(type, alias) \ - static icinga::RegisterTypeHelper g_RegisterDT_ ## type(alias, DynamicObjectFactory) + static icinga::RegisterTypeHelper g_RegisterDT_ ## type(alias, DynamicObjectFactory); #define REGISTER_TYPE(type) \ REGISTER_TYPE_ALIAS(type, #type) diff --git a/lib/base/scriptfunction.h b/lib/base/scriptfunction.h index 50bffc6e7..049bee52b 100644 --- a/lib/base/scriptfunction.h +++ b/lib/base/scriptfunction.h @@ -78,7 +78,9 @@ public: }; #define REGISTER_SCRIPTFUNCTION(name, callback) \ - static icinga::RegisterFunctionHelper g_RegisterSF_ ## type(name, callback) + static icinga::RegisterFunctionHelper g_RegisterSF_ ## name(#name, callback) + +#undef MKSYMBOL } diff --git a/lib/base/utility.h b/lib/base/utility.h index 37827f065..b8bafbb06 100644 --- a/lib/base/utility.h +++ b/lib/base/utility.h @@ -90,4 +90,6 @@ private: # define ASSERT(expr) #endif /* _DEBUG */ +#define CONCAT(a, b) a ## b + #endif /* UTILITY_H */ diff --git a/lib/icinga/Makefile.am b/lib/icinga/Makefile.am index 242f64db9..6698cfb21 100644 --- a/lib/icinga/Makefile.am +++ b/lib/icinga/Makefile.am @@ -50,6 +50,8 @@ libicinga_la_SOURCES = \ service.h \ servicegroup.cpp \ servicegroup.h \ + timeperiod.cpp \ + timeperiod.h \ user.cpp \ user.h \ usergroup.cpp \ diff --git a/lib/icinga/api.cpp b/lib/icinga/api.cpp index 431796906..837efc4f3 100644 --- a/lib/icinga/api.cpp +++ b/lib/icinga/api.cpp @@ -21,7 +21,7 @@ using namespace icinga; -REGISTER_SCRIPTFUNCTION("GetAnswerToEverything", &API::GetAnswerToEverything); +REGISTER_SCRIPTFUNCTION(GetAnswerToEverything, &API::GetAnswerToEverything); /** * @threadsafety Always. diff --git a/lib/icinga/host.cpp b/lib/icinga/host.cpp index 408d69f61..c02136a62 100644 --- a/lib/icinga/host.cpp +++ b/lib/icinga/host.cpp @@ -26,7 +26,7 @@ map > Host::m_ServicesCache; bool Host::m_ServicesCacheNeedsUpdate = false; Timer::Ptr Host::m_ServicesCacheTimer; -REGISTER_SCRIPTFUNCTION("ValidateServiceDictionary", &Host::ValidateServiceDictionary); +REGISTER_SCRIPTFUNCTION(ValidateServiceDictionary, &Host::ValidateServiceDictionary); REGISTER_TYPE(Host); @@ -191,6 +191,10 @@ static void CopyServiceAttributes(TDict serviceDesc, const ConfigItemBuilder::Pt if (!notification_interval.IsEmpty()) builder->AddExpression("notification_interval", OperatorSet, notification_interval); + Value check_period = serviceDesc->Get("check_period"); + if (!check_period.IsEmpty()) + builder->AddExpression("check_period", OperatorSet, check_period); + if (copyServiceAttrs) { Value servicedependencies = serviceDesc->Get("servicedependencies"); if (!servicedependencies.IsEmpty()) diff --git a/lib/icinga/i2-icinga.h b/lib/icinga/i2-icinga.h index 393198820..608ee8211 100644 --- a/lib/icinga/i2-icinga.h +++ b/lib/icinga/i2-icinga.h @@ -42,10 +42,10 @@ using boost::algorithm::is_any_of; #include "externalcommandprocessor.h" -#include "endpoint.h" -#include "endpointmanager.h" #include "icingaapplication.h" +#include "timeperiod.h" + #include "user.h" #include "usergroup.h" diff --git a/lib/icinga/icinga-type.conf b/lib/icinga/icinga-type.conf index 2418e6fd4..7aaeac355 100644 --- a/lib/icinga/icinga-type.conf +++ b/lib/icinga/icinga-type.conf @@ -51,6 +51,7 @@ type Host { %attribute string "*" }, + %attribute string "check_period", %attribute number "check_interval", %attribute number "retry_interval", @@ -99,6 +100,7 @@ type Host { /* service attributes */ %attribute number "max_check_attempts", + %attribute string "check_period", %attribute number "check_interval", %attribute number "retry_interval", %attribute number "notification_interval", @@ -144,6 +146,7 @@ type Service { }, %attribute string "check_command", %attribute number "max_check_attempts", + %attribute string "check_period", %attribute number "check_interval", %attribute number "retry_interval", %attribute dictionary "hostdependencies" { @@ -248,3 +251,11 @@ type UserGroup { %attribute string "action_url", %attribute string "notes_url" } + +type TimePeriod { + %require "methods", + %attribute dictionary "methods" { + %require "update", + %attribute string "update" + }, +} diff --git a/lib/icinga/icinga.vcxproj b/lib/icinga/icinga.vcxproj index d91d2bfc5..52b5341e6 100644 --- a/lib/icinga/icinga.vcxproj +++ b/lib/icinga/icinga.vcxproj @@ -50,6 +50,7 @@ + @@ -70,6 +71,7 @@ + @@ -240,4 +242,4 @@ - \ No newline at end of file + diff --git a/lib/icinga/notification.cpp b/lib/icinga/notification.cpp index 11d92ef3c..075f5aeff 100644 --- a/lib/icinga/notification.cpp +++ b/lib/icinga/notification.cpp @@ -213,8 +213,7 @@ void Notification::BeginExecuteNotificationHelper(const Dictionary::Ptr& notific arguments.push_back(macros); arguments.push_back(type); - ScriptTask::Ptr task; - task = MakeMethodTask("notify", arguments); + ScriptTask::Ptr task = MakeMethodTask("notify", arguments); if (!task) { Logger::Write(LogWarning, "icinga", "Notification object '" + GetName() + "' doesn't have a 'notify' method."); diff --git a/lib/icinga/nullchecktask.cpp b/lib/icinga/nullchecktask.cpp index fef26eeae..f413ba896 100644 --- a/lib/icinga/nullchecktask.cpp +++ b/lib/icinga/nullchecktask.cpp @@ -21,7 +21,7 @@ using namespace icinga; -REGISTER_SCRIPTFUNCTION("NullCheck", &NullCheckTask::ScriptFunc); +REGISTER_SCRIPTFUNCTION(NullCheck, &NullCheckTask::ScriptFunc); /** * @threadsafety Always. diff --git a/lib/icinga/pluginchecktask.cpp b/lib/icinga/pluginchecktask.cpp index 814a1ce82..88e0855f2 100644 --- a/lib/icinga/pluginchecktask.cpp +++ b/lib/icinga/pluginchecktask.cpp @@ -21,7 +21,7 @@ using namespace icinga; -REGISTER_SCRIPTFUNCTION("PluginCheck", &PluginCheckTask::ScriptFunc); +REGISTER_SCRIPTFUNCTION(PluginCheck, &PluginCheckTask::ScriptFunc); PluginCheckTask::PluginCheckTask(const ScriptTask::Ptr& task, const Process::Ptr& process, const Value& command) : m_Task(task), m_Process(process), m_Command(command) diff --git a/lib/icinga/pluginnotificationtask.cpp b/lib/icinga/pluginnotificationtask.cpp index 305b97426..74d1f7e6c 100644 --- a/lib/icinga/pluginnotificationtask.cpp +++ b/lib/icinga/pluginnotificationtask.cpp @@ -21,7 +21,7 @@ using namespace icinga; -REGISTER_SCRIPTFUNCTION("PluginNotification", &PluginNotificationTask::ScriptFunc); +REGISTER_SCRIPTFUNCTION(PluginNotification, &PluginNotificationTask::ScriptFunc); PluginNotificationTask::PluginNotificationTask(const ScriptTask::Ptr& task, const Process::Ptr& process, const String& service, const String& command) diff --git a/lib/icinga/service-check.cpp b/lib/icinga/service-check.cpp index d20bf44a8..f4315a140 100644 --- a/lib/icinga/service-check.cpp +++ b/lib/icinga/service-check.cpp @@ -47,6 +47,14 @@ long Service::GetMaxCheckAttempts(void) const return m_MaxCheckAttempts; } +/** + * @threadsafety Always. + */ +TimePeriod::Ptr Service::GetCheckPeriod(void) const +{ + return TimePeriod::GetByName(m_CheckPeriod); +} + /** * @threadsafety Always. */ diff --git a/lib/icinga/service.cpp b/lib/icinga/service.cpp index 3be6ac0ca..37a14d79d 100644 --- a/lib/icinga/service.cpp +++ b/lib/icinga/service.cpp @@ -35,6 +35,7 @@ Service::Service(const Dictionary::Ptr& serializedObject) RegisterAttribute("check_command", Attribute_Config, &m_CheckCommand); RegisterAttribute("max_check_attempts", Attribute_Config, &m_MaxCheckAttempts); + RegisterAttribute("check_period", Attribute_Config, &m_CheckPeriod); RegisterAttribute("check_interval", Attribute_Config, &m_CheckInterval); RegisterAttribute("retry_interval", Attribute_Config, &m_RetryInterval); RegisterAttribute("checkers", Attribute_Config, &m_Checkers); diff --git a/lib/icinga/service.h b/lib/icinga/service.h index 9400e0f46..1666d9dfd 100644 --- a/lib/icinga/service.h +++ b/lib/icinga/service.h @@ -110,6 +110,7 @@ public: Dictionary::Ptr GetCheckers(void) const; Value GetCheckCommand(void) const; long GetMaxCheckAttempts(void) const; + TimePeriod::Ptr GetCheckPeriod(void) const; double GetCheckInterval(void) const; double GetRetryInterval(void) const; @@ -264,6 +265,7 @@ private: /* Checks */ Attribute m_CheckCommand; Attribute m_MaxCheckAttempts; + Attribute m_CheckPeriod; Attribute m_CheckInterval; Attribute m_RetryInterval; Attribute m_NextCheck; diff --git a/lib/icinga/timeperiod.cpp b/lib/icinga/timeperiod.cpp new file mode 100644 index 000000000..467869ad6 --- /dev/null +++ b/lib/icinga/timeperiod.cpp @@ -0,0 +1,280 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012 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 "i2-icinga.h" + +using namespace icinga; + +REGISTER_TYPE(TimePeriod); +REGISTER_SCRIPTFUNCTION(EmptyTimePeriod, &TimePeriod::EmptyTimePeriodUpdate); +REGISTER_SCRIPTFUNCTION(EvenMinutesTimePeriod, &TimePeriod::EvenMinutesTimePeriodUpdate); + +Timer::Ptr TimePeriod::m_UpdateTimer; + +TimePeriod::TimePeriod(const Dictionary::Ptr& serializedUpdate) + : DynamicObject(serializedUpdate) +{ + RegisterAttribute("valid_begin", Attribute_Replicated, &m_ValidBegin); + RegisterAttribute("valid_end", Attribute_Replicated, &m_ValidEnd); + RegisterAttribute("segments", Attribute_Replicated, &m_Segments); + + if (!m_UpdateTimer) { + m_UpdateTimer = boost::make_shared(); + m_UpdateTimer->SetInterval(300); + m_UpdateTimer->OnTimerExpired.connect(boost::bind(&TimePeriod::UpdateTimerHandler)); + m_UpdateTimer->Start(); + } +} + +void TimePeriod::Start(void) +{ + /* Pre-fill the time period for the next 24 hours. */ + double now = Utility::GetTime(); + UpdateRegion(now, now + 24 * 3600); +} + +/** + * @threadsafety Always. + */ +TimePeriod::Ptr TimePeriod::GetByName(const String& name) +{ + DynamicObject::Ptr configObject = DynamicObject::GetObject("TimePeriod", name); + + return dynamic_pointer_cast(configObject); +} + +void TimePeriod::AddSegment(double begin, double end) +{ + ASSERT(OwnsLock()); + + if (m_ValidBegin.IsEmpty() || begin < m_ValidBegin) + m_ValidBegin = begin; + + if (m_ValidEnd.IsEmpty() || end > m_ValidEnd) + m_ValidEnd = end; + + Array::Ptr segments = m_Segments; + + if (segments) { + /* Try to merge the new segment into an existing segment. */ + ObjectLock dlock(segments); + BOOST_FOREACH(const Dictionary::Ptr& segment, segments) { + if (segment->Get("begin") <= begin && segment->Get("end") >= end) + return; /* New segment is fully contained in this segment. */ + + if (segment->Get("begin") < begin && segment->Get("end") > begin) { + segment->Set("end", end); /* Extend an existing segment. */ + return; + } + + if (segment->Get("begin") > begin && segment->Get("begin") < end) { + segment->Set("begin", begin); /* Extend an existing segment. */ + return; + } + } + } + + /* Create new segment if we weren't able to merge this into an existing segment. */ + Dictionary::Ptr segment = boost::make_shared(); + segment->Set("begin", begin); + segment->Set("end", end); + + if (!segments) { + segments = boost::make_shared(); + m_Segments = segments; + } + + segments->Add(segment); + Touch("segments"); +} + +void TimePeriod::RemoveSegment(double begin, double end) +{ + ASSERT(OwnsLock()); + + if (m_ValidBegin.IsEmpty() || begin < m_ValidBegin) + m_ValidBegin = begin; + + if (m_ValidEnd.IsEmpty() || end > m_ValidEnd) + m_ValidEnd = end; + + Array::Ptr segments = m_Segments; + + if (!segments) + return; + + /* Try to split or adjust an existing segment. */ + ObjectLock dlock(segments); + BOOST_FOREACH(const Dictionary::Ptr& segment, segments) { + BOOST_THROW_EXCEPTION(runtime_error("Not implemented.")); + } + + Touch("segments"); +} + +void TimePeriod::PurgeSegments(double end) +{ + ASSERT(OwnsLock()); + + if (m_ValidBegin.IsEmpty() || end < m_ValidBegin) + return; + + m_ValidBegin = end; + + Array::Ptr segments = m_Segments; + + if (!segments) + return; + + Array::Ptr newSegments = boost::make_shared(); + + /* Remove old segments. */ + ObjectLock dlock(segments); + BOOST_FOREACH(const Dictionary::Ptr& segment, segments) { + if (segment->Get("end") >= end) + newSegments->Add(segment); + } + + m_Segments = newSegments; + Touch("segments"); +} + +void TimePeriod::UpdateRegion(double begin, double end) +{ + if (begin < m_ValidEnd) + begin = m_ValidEnd; + + if (end < m_ValidEnd) + return; + + TimePeriod::Ptr self = GetSelf(); + + vector arguments; + arguments.push_back(self); + arguments.push_back(begin); + arguments.push_back(end); + ScriptTask::Ptr task = MakeMethodTask("update", arguments); + + if (!task) { + Logger::Write(LogWarning, "icinga", "TimePeriod object '" + GetName() + "' doesn't have an 'update' method."); + + return; + } + + task->Start(); + task->GetResult(); +} + +bool TimePeriod::IsInside(double ts) const +{ + ObjectLock olock(this); + + if (m_ValidBegin.IsEmpty() || ts < m_ValidBegin || m_ValidEnd.IsEmpty() || ts > m_ValidEnd) + return true; /* Assume that all invalid regions are "inside". */ + + Array::Ptr segments = m_Segments; + + if (segments) { + ObjectLock dlock(segments); + BOOST_FOREACH(const Dictionary::Ptr& segment, segments) { + if (ts > segment->Get("begin") && ts < segment->Get("end")) + return true; + } + } + + return false; +} + +double TimePeriod::FindNextTransition(double begin) +{ + ObjectLock olock(this); + + Array::Ptr segments = m_Segments; + + double closestTransition = -1; + + if (segments) { + ObjectLock dlock(segments); + BOOST_FOREACH(const Dictionary::Ptr& segment, segments) { + if (segment->Get("begin") > begin && (segment->Get("begin") < closestTransition || closestTransition == -1)) + closestTransition = segment->Get("begin"); + + if (segment->Get("end") > begin && (segment->Get("end") < closestTransition || closestTransition == -1)) + closestTransition = segment->Get("end"); + } + } + + return closestTransition; +} + +void TimePeriod::UpdateTimerHandler(void) +{ + double now = Utility::GetTime(); + + BOOST_FOREACH(const DynamicObject::Ptr& object, DynamicType::GetObjects("TimePeriod")) { + TimePeriod::Ptr tp = static_pointer_cast(object); + + /* Only update time periods that have been defined on this node. */ + if (!ConfigItem::GetObject("TimePeriod", tp->GetName())) + continue; + + tp->PurgeSegments(now - 3600); + + if (tp->m_ValidEnd < now + 3 * 3600) + tp->UpdateRegion(tp->m_ValidEnd, tp->m_ValidEnd + 24 * 3600); + } +} + +void TimePeriod::EmptyTimePeriodUpdate(const ScriptTask::Ptr& task, const vector& arguments) +{ + if (arguments.size() < 3) + BOOST_THROW_EXCEPTION(runtime_error("Expected 3 arguments.")); + + TimePeriod::Ptr tp = arguments[0]; + double begin = arguments[1]; + double end = arguments[2]; + + ObjectLock olock(tp); + tp->RemoveSegment(begin, end); + + task->FinishResult(Empty); +} + +void TimePeriod::EvenMinutesTimePeriodUpdate(const ScriptTask::Ptr& task, const vector& arguments) +{ + if (arguments.size() < 3) + BOOST_THROW_EXCEPTION(runtime_error("Expected 3 arguments.")); + + TimePeriod::Ptr tp = arguments[0]; + double begin = arguments[1]; + double end = arguments[2]; + + { + ObjectLock olock(tp); + + tp->RemoveSegment(begin, end); + + for (long t = begin; t < end; t += 60) { + if ((t / 60) % 2 == 0) + tp->AddSegment(t, t + 60); + } + } + + task->FinishResult(Empty); +} diff --git a/lib/icinga/timeperiod.h b/lib/icinga/timeperiod.h new file mode 100644 index 000000000..50b1d1fe4 --- /dev/null +++ b/lib/icinga/timeperiod.h @@ -0,0 +1,66 @@ +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012 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 TIMEPERIOD_H +#define TIMEPERIOD_H + +namespace icinga +{ + +/** + * A time period. + * + * @ingroup icinga + */ +class I2_ICINGA_API TimePeriod : public DynamicObject +{ +public: + typedef shared_ptr Ptr; + typedef weak_ptr WeakPtr; + + TimePeriod(const Dictionary::Ptr& serializedUpdate); + + static TimePeriod::Ptr GetByName(const String& name); + + virtual void Start(void); + + void AddSegment(double s, double end); + void RemoveSegment(double begin, double end); + void PurgeSegments(double end); + + void UpdateRegion(double begin, double end); + + bool IsInside(double ts) const; + double FindNextTransition(double begin); + + static void EmptyTimePeriodUpdate(const ScriptTask::Ptr& task, const vector& arguments); + static void EvenMinutesTimePeriodUpdate(const ScriptTask::Ptr& task, const vector& arguments); + +private: + Attribute m_ValidBegin; + Attribute m_ValidEnd; + Attribute m_Segments; + + static Timer::Ptr m_UpdateTimer; + static void UpdateTimerHandler(void); +}; + +} + +#endif /* TIMEPERIOD_H */ -- 2.40.0