icinga2/lib/icinga/downtime.cpp
Michael Friedrich 8d56f8e951 Fix that host downtimes might be triggered even if their state is Up
If the checkresult's state is 1, this is internally mapped to Up.
The added downtime will check for triggers and might cause a wrong
downtime start event.

refs #5202
2017-05-11 15:05:50 +02:00

427 lines
12 KiB
C++

/******************************************************************************
* Icinga 2 *
* Copyright (C) 2012-2017 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/downtime.hpp"
#include "icinga/downtime.tcpp"
#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/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
using namespace icinga;
static int l_NextDowntimeID = 1;
static boost::mutex l_DowntimeMutex;
static std::map<int, String> l_LegacyDowntimesCache;
static Timer::Ptr l_DowntimesExpireTimer;
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;
INITIALIZE_ONCE(&Downtime::StaticInitialize);
REGISTER_TYPE(Downtime);
void Downtime::StaticInitialize(void)
{
l_DowntimesStartTimer = new Timer();
l_DowntimesStartTimer->SetInterval(5);
l_DowntimesStartTimer->OnTimerExpired.connect(boost::bind(&Downtime::DowntimesStartTimerHandler));
l_DowntimesStartTimer->Start();
l_DowntimesExpireTimer = new Timer();
l_DowntimesExpireTimer->SetInterval(60);
l_DowntimesExpireTimer->OnTimerExpired.connect(boost::bind(&Downtime::DowntimesExpireTimerHandler));
l_DowntimesExpireTimer->Start();
}
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;
boost::algorithm::split(tokens, name, boost::is_any_of("!"));
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(void)
{
ObjectImpl<Downtime>::OnAllConfigLoaded();
Host::Ptr host = Host::GetByName(GetHostName());
if (GetServiceName().IsEmpty())
m_Checkable = host;
else
m_Checkable = host->GetServiceByShortName(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);
{
boost::mutex::scoped_lock lock(l_DowntimeMutex);
SetLegacyId(l_NextDowntimeID);
l_LegacyDowntimesCache[l_NextDowntimeID] = GetName();
l_NextDowntimeID++;
}
Checkable::Ptr checkable = GetCheckable();
checkable->RegisterDowntime(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 (!checkable->IsStateOK(checkable->GetStateRaw())) {
Log(LogNotice, "Downtime")
<< "Checkable '" << checkable->GetName() << "' already in a NOT-OK state."
<< " Triggering downtime now.";
TriggerDowntime();
}
}
void Downtime::Stop(bool runtimeRemoved)
{
GetCheckable()->UnregisterDowntime(this);
if (runtimeRemoved)
OnDowntimeRemoved(this);
ObjectImpl<Downtime>::Stop(runtimeRemoved);
}
Checkable::Ptr Downtime::GetCheckable(void) const
{
return static_pointer_cast<Checkable>(m_Checkable);
}
bool Downtime::IsInEffect(void) const
{
double now = Utility::GetTime();
if (now < GetStartTime() ||
now > GetEndTime())
return false;
if (GetFixed())
return true;
double triggerTime = GetTriggerTime();
if (triggerTime == 0)
return false;
return (now < triggerTime + GetDuration());
}
bool Downtime::IsTriggered(void) const
{
double now = Utility::GetTime();
double triggerTime = GetTriggerTime();
return (triggerTime > 0 && triggerTime <= now);
}
bool Downtime::IsExpired(void) 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(void) const
{
String configOwner = GetConfigOwner();
return configOwner.IsEmpty() || GetObject<ScheduledDowntime>(configOwner);
}
int Downtime::GetNextDowntimeID(void)
{
boost::mutex::scoped_lock lock(l_DowntimeMutex);
return l_NextDowntimeID;
}
String 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& 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("config_owner", scheduledDowntime);
attrs->Set("entry_time", Utility::GetTime());
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 = checkable->GetZoneName();
if (!zone.IsEmpty())
attrs->Set("zone", zone);
String config = ConfigObjectUtility::CreateObjectConfig(Downtime::TypeInstance, fullName, true, Array::Ptr(), attrs);
Array::Ptr errors = new Array();
if (!ConfigObjectUtility::CreateObject(Downtime::TypeInstance, fullName, config, errors)) {
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(LogNotice, "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) << "'.";
return fullName;
}
void Downtime::RemoveDowntime(const String& id, bool cancelled, bool expired, 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) {
Log(LogWarning, "Downtime")
<< "Cannot remove downtime '" << downtime->GetName() << "'. It is owned by scheduled downtime object '" << config_owner << "'";
return;
}
downtime->SetWasCancelled(cancelled);
Log(LogNotice, "Downtime")
<< "Removed downtime '" << downtime->GetName() << "' from object '" << downtime->GetCheckable()->GetName() << "'.";
Array::Ptr errors = new Array();
if (!ConfigObjectUtility::DeleteObject(downtime, false, errors)) {
ObjectLock olock(errors);
for (const String& error : errors) {
Log(LogCritical, "Downtime", error);
}
BOOST_THROW_EXCEPTION(std::runtime_error("Could not remove downtime."));
}
}
bool Downtime::CanBeTriggered(void)
{
if (IsInEffect() && IsTriggered())
return false;
if (IsExpired())
return false;
double now = Utility::GetTime();
if (now < GetStartTime() || now > GetEndTime())
return false;
return true;
}
void Downtime::TriggerDowntime(void)
{
if (!CanBeTriggered())
return;
Log(LogNotice, "Downtime")
<< "Triggering downtime '" << GetName() << "'.";
if (GetTriggerTime() == 0)
SetTriggerTime(Utility::GetTime());
Array::Ptr triggers = GetTriggers();
{
ObjectLock olock(triggers);
for (const String& triggerName : triggers) {
Downtime::Ptr downtime = Downtime::GetByName(triggerName);
if (!downtime)
continue;
downtime->TriggerDowntime();
}
}
OnDowntimeTriggered(this);
}
String Downtime::GetDowntimeIDFromLegacyID(int id)
{
boost::mutex::scoped_lock lock(l_DowntimeMutex);
auto it = l_LegacyDowntimesCache.find(id);
if (it == l_LegacyDowntimesCache.end())
return Empty;
return it->second;
}
void Downtime::DowntimesStartTimerHandler(void)
{
/* 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();
}
}
}
void Downtime::DowntimesExpireTimerHandler(void)
{
std::vector<Downtime::Ptr> downtimes;
for (const Downtime::Ptr& downtime : ConfigType::GetObjectsByType<Downtime>()) {
downtimes.push_back(downtime);
}
for (const Downtime::Ptr& downtime : downtimes) {
/* Only remove downtimes which are activated after daemon start. */
if (downtime->IsActive() && (downtime->IsExpired() || !downtime->HasValidConfigOwner()))
RemoveDowntime(downtime->GetName(), false, true);
}
}
void Downtime::ValidateStartTime(const Timestamp& value, const ValidationUtils& utils)
{
ObjectImpl<Downtime>::ValidateStartTime(value, utils);
if (value <= 0)
BOOST_THROW_EXCEPTION(ValidationError(this, boost::assign::list_of("start_time"), "Start time must be greater than 0."));
}
void Downtime::ValidateEndTime(const Timestamp& value, const ValidationUtils& utils)
{
ObjectImpl<Downtime>::ValidateEndTime(value, utils);
if (value <= 0)
BOOST_THROW_EXCEPTION(ValidationError(this, boost::assign::list_of("end_time"), "End time must be greater than 0."));
}