mirror of
				https://github.com/Icinga/icinga2.git
				synced 2025-11-03 21:25:56 +01:00 
			
		
		
		
	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
		
			
				
	
	
		
			427 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			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."));
 | 
						|
}
 |