summaryrefslogtreecommitdiffstats
path: root/lib/icinga/downtime.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'lib/icinga/downtime.cpp')
-rw-r--r--lib/icinga/downtime.cpp584
1 files changed, 584 insertions, 0 deletions
diff --git a/lib/icinga/downtime.cpp b/lib/icinga/downtime.cpp
new file mode 100644
index 0000000..2178953
--- /dev/null
+++ b/lib/icinga/downtime.cpp
@@ -0,0 +1,584 @@
+/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
+
+#include "icinga/downtime.hpp"
+#include "icinga/downtime-ti.cpp"
+#include "icinga/host.hpp"
+#include "icinga/scheduleddowntime.hpp"
+#include "remote/configobjectutility.hpp"
+#include "base/configtype.hpp"
+#include "base/utility.hpp"
+#include "base/timer.hpp"
+#include <boost/thread/once.hpp>
+#include <cmath>
+#include <utility>
+
+using namespace icinga;
+
+static int l_NextDowntimeID = 1;
+static std::mutex l_DowntimeMutex;
+static std::map<int, String> l_LegacyDowntimesCache;
+static Timer::Ptr l_DowntimesOrphanedTimer;
+static Timer::Ptr l_DowntimesStartTimer;
+
+boost::signals2::signal<void (const Downtime::Ptr&)> Downtime::OnDowntimeAdded;
+boost::signals2::signal<void (const Downtime::Ptr&)> Downtime::OnDowntimeRemoved;
+boost::signals2::signal<void (const Downtime::Ptr&)> Downtime::OnDowntimeStarted;
+boost::signals2::signal<void (const Downtime::Ptr&)> Downtime::OnDowntimeTriggered;
+boost::signals2::signal<void (const Downtime::Ptr&, const String&, double, const MessageOrigin::Ptr&)> Downtime::OnRemovalInfoChanged;
+
+REGISTER_TYPE(Downtime);
+
+INITIALIZE_ONCE(&Downtime::StaticInitialize);
+
+void Downtime::StaticInitialize()
+{
+ ScriptGlobal::Set("Icinga.DowntimeNoChildren", "DowntimeNoChildren");
+ ScriptGlobal::Set("Icinga.DowntimeTriggeredChildren", "DowntimeTriggeredChildren");
+ ScriptGlobal::Set("Icinga.DowntimeNonTriggeredChildren", "DowntimeNonTriggeredChildren");
+}
+
+String DowntimeNameComposer::MakeName(const String& shortName, const Object::Ptr& context) const
+{
+ Downtime::Ptr downtime = dynamic_pointer_cast<Downtime>(context);
+
+ if (!downtime)
+ return "";
+
+ String name = downtime->GetHostName();
+
+ if (!downtime->GetServiceName().IsEmpty())
+ name += "!" + downtime->GetServiceName();
+
+ name += "!" + shortName;
+
+ return name;
+}
+
+Dictionary::Ptr DowntimeNameComposer::ParseName(const String& name) const
+{
+ std::vector<String> tokens = name.Split("!");
+
+ if (tokens.size() < 2)
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid Downtime 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 Downtime::OnAllConfigLoaded()
+{
+ ObjectImpl<Downtime>::OnAllConfigLoaded();
+
+ if (GetServiceName().IsEmpty())
+ m_Checkable = Host::GetByName(GetHostName());
+ else
+ m_Checkable = Service::GetByNamePair(GetHostName(), GetServiceName());
+
+ if (!m_Checkable)
+ BOOST_THROW_EXCEPTION(ScriptError("Downtime '" + GetName() + "' references a host/service which doesn't exist.", GetDebugInfo()));
+}
+
+void Downtime::Start(bool runtimeCreated)
+{
+ ObjectImpl<Downtime>::Start(runtimeCreated);
+
+ static boost::once_flag once = BOOST_ONCE_INIT;
+
+ boost::call_once(once, [this]() {
+ l_DowntimesStartTimer = Timer::Create();
+ l_DowntimesStartTimer->SetInterval(5);
+ l_DowntimesStartTimer->OnTimerExpired.connect([](const Timer * const&){ DowntimesStartTimerHandler(); });
+ l_DowntimesStartTimer->Start();
+
+ l_DowntimesOrphanedTimer = Timer::Create();
+ l_DowntimesOrphanedTimer->SetInterval(60);
+ l_DowntimesOrphanedTimer->OnTimerExpired.connect([](const Timer * const&) { DowntimesOrphanedTimerHandler(); });
+ l_DowntimesOrphanedTimer->Start();
+ });
+
+ {
+ std::unique_lock<std::mutex> lock(l_DowntimeMutex);
+
+ SetLegacyId(l_NextDowntimeID);
+ l_LegacyDowntimesCache[l_NextDowntimeID] = GetName();
+ l_NextDowntimeID++;
+ }
+
+ Checkable::Ptr checkable = GetCheckable();
+
+ checkable->RegisterDowntime(this);
+
+ Downtime::Ptr parent = GetByName(GetParent());
+
+ if (parent)
+ parent->RegisterChild(this);
+
+ if (runtimeCreated)
+ OnDowntimeAdded(this);
+
+ /* if this object is already in a NOT-OK state trigger
+ * this downtime now *after* it has been added (important
+ * for DB IDO, etc.)
+ */
+ if (!GetFixed() && !checkable->IsStateOK(checkable->GetStateRaw())) {
+ Log(LogNotice, "Downtime")
+ << "Checkable '" << checkable->GetName() << "' already in a NOT-OK state."
+ << " Triggering downtime now.";
+
+ TriggerDowntime(std::fmax(std::fmax(GetStartTime(), GetEntryTime()), checkable->GetLastStateChange()));
+ }
+
+ if (GetFixed() && CanBeTriggered()) {
+ /* Send notifications. */
+ OnDowntimeStarted(this);
+
+ /* Trigger fixed downtime immediately. */
+ TriggerDowntime(std::fmax(GetStartTime(), GetEntryTime()));
+ }
+}
+
+void Downtime::Stop(bool runtimeRemoved)
+{
+ GetCheckable()->UnregisterDowntime(this);
+
+ Downtime::Ptr parent = GetByName(GetParent());
+
+ if (parent)
+ parent->UnregisterChild(this);
+
+ if (runtimeRemoved)
+ OnDowntimeRemoved(this);
+
+ ObjectImpl<Downtime>::Stop(runtimeRemoved);
+}
+
+void Downtime::Pause()
+{
+ if (m_CleanupTimer) {
+ m_CleanupTimer->Stop();
+ }
+
+ ObjectImpl<Downtime>::Pause();
+}
+
+void Downtime::Resume()
+{
+ ObjectImpl<Downtime>::Resume();
+ SetupCleanupTimer();
+}
+
+Checkable::Ptr Downtime::GetCheckable() const
+{
+ return static_pointer_cast<Checkable>(m_Checkable);
+}
+
+bool Downtime::IsInEffect() const
+{
+ double now = Utility::GetTime();
+
+ if (GetFixed()) {
+ /* fixed downtimes are in effect during the entire [start..end) interval */
+ return (now >= GetStartTime() && now < GetEndTime());
+ }
+
+ double triggerTime = GetTriggerTime();
+
+ if (triggerTime == 0)
+ /* flexible downtime has not been triggered yet */
+ return false;
+
+ return (now < triggerTime + GetDuration());
+}
+
+bool Downtime::IsTriggered() const
+{
+ double now = Utility::GetTime();
+
+ double triggerTime = GetTriggerTime();
+
+ return (triggerTime > 0 && triggerTime <= now);
+}
+
+bool Downtime::IsExpired() const
+{
+ double now = Utility::GetTime();
+
+ if (GetFixed())
+ return (GetEndTime() < now);
+ else {
+ /* triggered flexible downtime not in effect anymore */
+ if (IsTriggered() && !IsInEffect())
+ return true;
+ /* flexible downtime never triggered */
+ else if (!IsTriggered() && (GetEndTime() < now))
+ return true;
+ else
+ return false;
+ }
+}
+
+bool Downtime::HasValidConfigOwner() const
+{
+ if (!ScheduledDowntime::AllConfigIsLoaded()) {
+ return true;
+ }
+
+ String configOwner = GetConfigOwner();
+ return configOwner.IsEmpty() || Zone::GetByName(GetAuthoritativeZone()) != Zone::GetLocalZone() || GetObject<ScheduledDowntime>(configOwner);
+}
+
+int Downtime::GetNextDowntimeID()
+{
+ std::unique_lock<std::mutex> lock(l_DowntimeMutex);
+
+ return l_NextDowntimeID;
+}
+
+Downtime::Ptr Downtime::AddDowntime(const Checkable::Ptr& checkable, const String& author,
+ const String& comment, double startTime, double endTime, bool fixed,
+ const String& triggeredBy, double duration,
+ const String& scheduledDowntime, const String& scheduledBy, const String& parent,
+ const String& id, const MessageOrigin::Ptr& origin)
+{
+ String fullName;
+
+ if (id.IsEmpty())
+ fullName = checkable->GetName() + "!" + Utility::NewUniqueID();
+ else
+ fullName = id;
+
+ Dictionary::Ptr attrs = new Dictionary();
+
+ attrs->Set("author", author);
+ attrs->Set("comment", comment);
+ attrs->Set("start_time", startTime);
+ attrs->Set("end_time", endTime);
+ attrs->Set("fixed", fixed);
+ attrs->Set("duration", duration);
+ attrs->Set("triggered_by", triggeredBy);
+ attrs->Set("scheduled_by", scheduledBy);
+ attrs->Set("parent", parent);
+ attrs->Set("config_owner", scheduledDowntime);
+ attrs->Set("entry_time", Utility::GetTime());
+
+ if (!scheduledDowntime.IsEmpty()) {
+ auto localZone (Zone::GetLocalZone());
+
+ if (localZone) {
+ attrs->Set("authoritative_zone", localZone->GetName());
+ }
+
+ auto sd (ScheduledDowntime::GetByName(scheduledDowntime));
+
+ if (sd) {
+ attrs->Set("config_owner_hash", sd->HashDowntimeOptions());
+ }
+ }
+
+ Host::Ptr host;
+ Service::Ptr service;
+ tie(host, service) = GetHostService(checkable);
+
+ attrs->Set("host_name", host->GetName());
+ if (service)
+ attrs->Set("service_name", service->GetShortName());
+
+ String zone;
+
+ if (!scheduledDowntime.IsEmpty()) {
+ auto sdt (ScheduledDowntime::GetByName(scheduledDowntime));
+
+ if (sdt) {
+ auto sdtZone (sdt->GetZone());
+
+ if (sdtZone) {
+ zone = sdtZone->GetName();
+ }
+ }
+ }
+
+ if (zone.IsEmpty()) {
+ zone = checkable->GetZoneName();
+ }
+
+ if (!zone.IsEmpty())
+ attrs->Set("zone", zone);
+
+ String config = ConfigObjectUtility::CreateObjectConfig(Downtime::TypeInstance, fullName, true, nullptr, attrs);
+
+ Array::Ptr errors = new Array();
+
+ if (!ConfigObjectUtility::CreateObject(Downtime::TypeInstance, fullName, config, errors, nullptr)) {
+ ObjectLock olock(errors);
+ for (const String& error : errors) {
+ Log(LogCritical, "Downtime", error);
+ }
+
+ BOOST_THROW_EXCEPTION(std::runtime_error("Could not create downtime."));
+ }
+
+ if (!triggeredBy.IsEmpty()) {
+ Downtime::Ptr parentDowntime = Downtime::GetByName(triggeredBy);
+ Array::Ptr triggers = parentDowntime->GetTriggers();
+
+ ObjectLock olock(triggers);
+ if (!triggers->Contains(fullName))
+ triggers->Add(fullName);
+ }
+
+ Downtime::Ptr downtime = Downtime::GetByName(fullName);
+
+ if (!downtime)
+ BOOST_THROW_EXCEPTION(std::runtime_error("Could not create downtime object."));
+
+ Log(LogInformation, "Downtime")
+ << "Added downtime '" << downtime->GetName()
+ << "' between '" << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S", startTime)
+ << "' and '" << Utility::FormatDateTime("%Y-%m-%d %H:%M:%S", endTime) << "', author: '"
+ << author << "', " << (fixed ? "fixed" : "flexible with " + Convert::ToString(duration) + "s duration");
+
+ return downtime;
+}
+
+void Downtime::RemoveDowntime(const String& id, bool includeChildren, bool cancelled, bool expired,
+ const String& removedBy, const MessageOrigin::Ptr& origin)
+{
+ Downtime::Ptr downtime = Downtime::GetByName(id);
+
+ if (!downtime || downtime->GetPackage() != "_api")
+ return;
+
+ String config_owner = downtime->GetConfigOwner();
+
+ if (!config_owner.IsEmpty() && !expired) {
+ BOOST_THROW_EXCEPTION(invalid_downtime_removal_error("Cannot remove downtime '" + downtime->GetName() +
+ "'. It is owned by scheduled downtime object '" + config_owner + "'"));
+ }
+
+ if (includeChildren) {
+ for (const Downtime::Ptr& child : downtime->GetChildren()) {
+ Downtime::RemoveDowntime(child->GetName(), true, true);
+ }
+ }
+
+ if (cancelled) {
+ downtime->SetRemovalInfo(removedBy, Utility::GetTime());
+ }
+
+ Array::Ptr errors = new Array();
+
+ if (!ConfigObjectUtility::DeleteObject(downtime, false, errors, nullptr)) {
+ ObjectLock olock(errors);
+ for (const String& error : errors) {
+ Log(LogCritical, "Downtime", error);
+ }
+
+ BOOST_THROW_EXCEPTION(std::runtime_error("Could not remove downtime."));
+ }
+
+ String reason;
+
+ if (expired) {
+ reason = "expired at " + Utility::FormatDateTime("%Y-%m-%d %H:%M:%S %z", downtime->GetEndTime());
+ } else if (cancelled) {
+ reason = "cancelled by user";
+ } else {
+ reason = "<unknown>";
+ }
+
+ Log msg (LogInformation, "Downtime");
+
+ msg << "Removed downtime '" << downtime->GetName() << "' from checkable";
+
+ {
+ auto checkable (downtime->GetCheckable());
+
+ if (checkable) {
+ msg << " '" << checkable->GetName() << "'";
+ }
+ }
+
+ msg << " (Reason: " << reason << ").";
+}
+
+void Downtime::RegisterChild(const Downtime::Ptr& downtime)
+{
+ std::unique_lock<std::mutex> lock(m_ChildrenMutex);
+ m_Children.insert(downtime);
+}
+
+void Downtime::UnregisterChild(const Downtime::Ptr& downtime)
+{
+ std::unique_lock<std::mutex> lock(m_ChildrenMutex);
+ m_Children.erase(downtime);
+}
+
+std::set<Downtime::Ptr> Downtime::GetChildren() const
+{
+ std::unique_lock<std::mutex> lock(m_ChildrenMutex);
+ return m_Children;
+}
+
+bool Downtime::CanBeTriggered()
+{
+ if (IsInEffect() && IsTriggered())
+ return false;
+
+ if (IsExpired())
+ return false;
+
+ double now = Utility::GetTime();
+
+ if (now < GetStartTime() || now > GetEndTime())
+ return false;
+
+ return true;
+}
+
+void Downtime::SetupCleanupTimer()
+{
+ if (!m_CleanupTimer) {
+ m_CleanupTimer = Timer::Create();
+
+ auto name (GetName());
+
+ m_CleanupTimer->OnTimerExpired.connect([name=std::move(name)](const Timer * const&) {
+ auto downtime (Downtime::GetByName(name));
+
+ if (downtime && downtime->IsExpired()) {
+ RemoveDowntime(name, false, false, true);
+ }
+ });
+ }
+
+ auto triggerTime (GetTriggerTime());
+
+ m_CleanupTimer->Reschedule((GetFixed() || triggerTime <= 0 ? GetEndTime() : triggerTime + GetDuration()) + 0.1);
+ m_CleanupTimer->Start();
+}
+
+void Downtime::TriggerDowntime(double triggerTime)
+{
+ if (!CanBeTriggered())
+ return;
+
+ Checkable::Ptr checkable = GetCheckable();
+
+ Log(LogInformation, "Downtime")
+ << "Triggering downtime '" << GetName() << "' for checkable '" << checkable->GetName() << "'.";
+
+ if (GetTriggerTime() == 0) {
+ SetTriggerTime(triggerTime);
+ }
+
+ {
+ ObjectLock olock (this);
+ SetupCleanupTimer();
+ }
+
+ Array::Ptr triggers = GetTriggers();
+
+ {
+ ObjectLock olock(triggers);
+ for (const String& triggerName : triggers) {
+ Downtime::Ptr downtime = Downtime::GetByName(triggerName);
+
+ if (!downtime)
+ continue;
+
+ downtime->TriggerDowntime(triggerTime);
+ }
+ }
+
+ OnDowntimeTriggered(this);
+}
+
+void Downtime::SetRemovalInfo(const String& removedBy, double removeTime, const MessageOrigin::Ptr& origin) {
+ {
+ ObjectLock olock(this);
+
+ SetRemovedBy(removedBy, false, origin);
+ SetRemoveTime(removeTime, false, origin);
+ }
+
+ OnRemovalInfoChanged(this, removedBy, removeTime, origin);
+}
+
+String Downtime::GetDowntimeIDFromLegacyID(int id)
+{
+ std::unique_lock<std::mutex> lock(l_DowntimeMutex);
+
+ auto it = l_LegacyDowntimesCache.find(id);
+
+ if (it == l_LegacyDowntimesCache.end())
+ return Empty;
+
+ return it->second;
+}
+
+void Downtime::DowntimesStartTimerHandler()
+{
+ /* Start fixed downtimes. Flexible downtimes will be triggered on-demand. */
+ for (const Downtime::Ptr& downtime : ConfigType::GetObjectsByType<Downtime>()) {
+ if (downtime->IsActive() &&
+ downtime->CanBeTriggered() &&
+ downtime->GetFixed()) {
+ /* Send notifications. */
+ OnDowntimeStarted(downtime);
+
+ /* Trigger fixed downtime immediately. */
+ downtime->TriggerDowntime(std::fmax(downtime->GetStartTime(), downtime->GetEntryTime()));
+ }
+ }
+}
+
+void Downtime::DowntimesOrphanedTimerHandler()
+{
+ for (const Downtime::Ptr& downtime : ConfigType::GetObjectsByType<Downtime>()) {
+ /* Only remove downtimes which are activated after daemon start. */
+ if (downtime->IsActive() && !downtime->HasValidConfigOwner())
+ RemoveDowntime(downtime->GetName(), false, false, true);
+ }
+}
+
+void Downtime::ValidateStartTime(const Lazy<Timestamp>& lvalue, const ValidationUtils& utils)
+{
+ ObjectImpl<Downtime>::ValidateStartTime(lvalue, utils);
+
+ if (lvalue() <= 0)
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "start_time" }, "Start time must be greater than 0."));
+}
+
+void Downtime::ValidateEndTime(const Lazy<Timestamp>& lvalue, const ValidationUtils& utils)
+{
+ ObjectImpl<Downtime>::ValidateEndTime(lvalue, utils);
+
+ if (lvalue() <= 0)
+ BOOST_THROW_EXCEPTION(ValidationError(this, { "end_time" }, "End time must be greater than 0."));
+}
+
+DowntimeChildOptions Downtime::ChildOptionsFromValue(const Value& options)
+{
+ if (options == "DowntimeNoChildren")
+ return DowntimeNoChildren;
+ else if (options == "DowntimeTriggeredChildren")
+ return DowntimeTriggeredChildren;
+ else if (options == "DowntimeNonTriggeredChildren")
+ return DowntimeNonTriggeredChildren;
+ else if (options.IsNumber()) {
+ int number = options;
+ if (number >= 0 && number <= 2)
+ return static_cast<DowntimeChildOptions>(number);
+ }
+
+ BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid child option specified"));
+}