mirror of
				https://github.com/Icinga/icinga2.git
				synced 2025-11-04 05:34:12 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			239 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			239 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
 | 
						|
 | 
						|
#include "icinga/service.hpp"
 | 
						|
#include "icinga/dependency.hpp"
 | 
						|
#include "base/logger.hpp"
 | 
						|
#include <unordered_map>
 | 
						|
 | 
						|
using namespace icinga;
 | 
						|
 | 
						|
/**
 | 
						|
 * The maximum number of dependency recursion levels allowed.
 | 
						|
 *
 | 
						|
 * This is a subjective limit how deep the dependency tree should be allowed to go, as anything beyond this level
 | 
						|
 * is just madness and will likely result in a stack overflow or other undefined behavior.
 | 
						|
 */
 | 
						|
static constexpr int l_MaxDependencyRecursionLevel(256);
 | 
						|
 | 
						|
void Checkable::AddDependency(const Dependency::Ptr& dep)
 | 
						|
{
 | 
						|
	std::unique_lock<std::mutex> lock(m_DependencyMutex);
 | 
						|
	m_Dependencies.insert(dep);
 | 
						|
}
 | 
						|
 | 
						|
void Checkable::RemoveDependency(const Dependency::Ptr& dep)
 | 
						|
{
 | 
						|
	std::unique_lock<std::mutex> lock(m_DependencyMutex);
 | 
						|
	m_Dependencies.erase(dep);
 | 
						|
}
 | 
						|
 | 
						|
std::vector<Dependency::Ptr> Checkable::GetDependencies() const
 | 
						|
{
 | 
						|
	std::unique_lock<std::mutex> lock(m_DependencyMutex);
 | 
						|
	return std::vector<Dependency::Ptr>(m_Dependencies.begin(), m_Dependencies.end());
 | 
						|
}
 | 
						|
 | 
						|
void Checkable::AddReverseDependency(const Dependency::Ptr& dep)
 | 
						|
{
 | 
						|
	std::unique_lock<std::mutex> lock(m_DependencyMutex);
 | 
						|
	m_ReverseDependencies.insert(dep);
 | 
						|
}
 | 
						|
 | 
						|
void Checkable::RemoveReverseDependency(const Dependency::Ptr& dep)
 | 
						|
{
 | 
						|
	std::unique_lock<std::mutex> lock(m_DependencyMutex);
 | 
						|
	m_ReverseDependencies.erase(dep);
 | 
						|
}
 | 
						|
 | 
						|
std::vector<Dependency::Ptr> Checkable::GetReverseDependencies() const
 | 
						|
{
 | 
						|
	std::unique_lock<std::mutex> lock(m_DependencyMutex);
 | 
						|
	return std::vector<Dependency::Ptr>(m_ReverseDependencies.begin(), m_ReverseDependencies.end());
 | 
						|
}
 | 
						|
 | 
						|
bool Checkable::IsReachable(DependencyType dt, Dependency::Ptr *failedDependency, int rstack) const
 | 
						|
{
 | 
						|
	if (rstack > l_MaxDependencyRecursionLevel) {
 | 
						|
		Log(LogWarning, "Checkable")
 | 
						|
			<< "Too many nested dependencies (>" << l_MaxDependencyRecursionLevel << ") for checkable '" << GetName() << "': Dependency failed.";
 | 
						|
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	for (const Checkable::Ptr& checkable : GetParents()) {
 | 
						|
		if (!checkable->IsReachable(dt, failedDependency, rstack + 1))
 | 
						|
			return false;
 | 
						|
	}
 | 
						|
 | 
						|
	/* implicit dependency on host if this is a service */
 | 
						|
	const auto *service = dynamic_cast<const Service *>(this);
 | 
						|
	if (service && (dt == DependencyState || dt == DependencyNotification)) {
 | 
						|
		Host::Ptr host = service->GetHost();
 | 
						|
 | 
						|
		if (host && host->GetState() != HostUp && host->GetStateType() == StateTypeHard) {
 | 
						|
			if (failedDependency)
 | 
						|
				*failedDependency = nullptr;
 | 
						|
 | 
						|
			return false;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	auto deps = GetDependencies();
 | 
						|
 | 
						|
	std::unordered_map<std::string, Dependency::Ptr> violated; // key: redundancy group, value: nullptr if satisfied, violating dependency otherwise
 | 
						|
 | 
						|
	for (const Dependency::Ptr& dep : deps) {
 | 
						|
		std::string redundancy_group = dep->GetRedundancyGroup();
 | 
						|
 | 
						|
		if (!dep->IsAvailable(dt)) {
 | 
						|
			if (redundancy_group.empty()) {
 | 
						|
				Log(LogDebug, "Checkable")
 | 
						|
					<< "Non-redundant dependency '" << dep->GetName() << "' failed for checkable '" << GetName() << "': Marking as unreachable.";
 | 
						|
 | 
						|
				if (failedDependency)
 | 
						|
					*failedDependency = dep;
 | 
						|
 | 
						|
				return false;
 | 
						|
			}
 | 
						|
 | 
						|
			// tentatively mark this dependency group as failed unless it is already marked;
 | 
						|
			//  so it either passed before (don't overwrite) or already failed (so don't care)
 | 
						|
			// note that std::unordered_map::insert() will not overwrite an existing entry
 | 
						|
			violated.insert(std::make_pair(redundancy_group, dep));
 | 
						|
		} else if (!redundancy_group.empty()) {
 | 
						|
			violated[redundancy_group] = nullptr;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	auto violator = std::find_if(violated.begin(), violated.end(), [](auto& v) { return v.second != nullptr; });
 | 
						|
	if (violator != violated.end()) {
 | 
						|
		Log(LogDebug, "Checkable")
 | 
						|
			<< "All dependencies in redundancy group '" << violator->first << "' have failed for checkable '" << GetName() << "': Marking as unreachable.";
 | 
						|
 | 
						|
		if (failedDependency)
 | 
						|
			*failedDependency = violator->second;
 | 
						|
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	if (failedDependency)
 | 
						|
		*failedDependency = nullptr;
 | 
						|
 | 
						|
	return true;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Checks whether the last check result of this Checkable affects its child dependencies.
 | 
						|
 *
 | 
						|
 * A Checkable affects its child dependencies if it runs into a non-OK state and results in any of its child
 | 
						|
 * Checkables to become unreachable. Though, that unavailable dependency may not necessarily cause the child
 | 
						|
 * Checkable to be in unreachable state as it might have some other dependencies that are still reachable, instead
 | 
						|
 * it just indicates whether the edge/connection between this and the child Checkable is broken or not.
 | 
						|
 *
 | 
						|
 * @return bool - Returns true if the Checkable affects its child dependencies, otherwise false.
 | 
						|
 */
 | 
						|
bool Checkable::AffectsChildren() const
 | 
						|
{
 | 
						|
	if (!GetLastCheckResult() || !IsReachable()) {
 | 
						|
		// If there is no check result, or the Checkable is not reachable, we can't safely determine whether
 | 
						|
		// the Checkable affects its child dependencies.
 | 
						|
		return false;
 | 
						|
	}
 | 
						|
 | 
						|
	for (auto& dep : GetReverseDependencies()) {
 | 
						|
		if (!dep->IsAvailable(DependencyState)) {
 | 
						|
			// If one of the child dependency is not available, then it's definitely due to the
 | 
						|
			// current Checkable state, so we don't need to verify the remaining ones.
 | 
						|
			return true;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return false;
 | 
						|
}
 | 
						|
 | 
						|
std::set<Checkable::Ptr> Checkable::GetParents() const
 | 
						|
{
 | 
						|
	std::set<Checkable::Ptr> parents;
 | 
						|
 | 
						|
	for (const Dependency::Ptr& dep : GetDependencies()) {
 | 
						|
		Checkable::Ptr parent = dep->GetParent();
 | 
						|
 | 
						|
		if (parent && parent.get() != this)
 | 
						|
			parents.insert(parent);
 | 
						|
	}
 | 
						|
 | 
						|
	return parents;
 | 
						|
}
 | 
						|
 | 
						|
std::set<Checkable::Ptr> Checkable::GetChildren() const
 | 
						|
{
 | 
						|
	std::set<Checkable::Ptr> parents;
 | 
						|
 | 
						|
	for (const Dependency::Ptr& dep : GetReverseDependencies()) {
 | 
						|
		Checkable::Ptr service = dep->GetChild();
 | 
						|
 | 
						|
		if (service && service.get() != this)
 | 
						|
			parents.insert(service);
 | 
						|
	}
 | 
						|
 | 
						|
	return parents;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Retrieve the total number of all the children of the current Checkable.
 | 
						|
 *
 | 
						|
 * Note, due to the max recursion limit of 256, the returned number may not reflect
 | 
						|
 * the actual total number of children involved in the dependency chain.
 | 
						|
 *
 | 
						|
 * @return int - Returns the total number of all the children of the current Checkable.
 | 
						|
 */
 | 
						|
size_t Checkable::GetAllChildrenCount() const
 | 
						|
{
 | 
						|
	// Are you thinking in making this more efficient? Please, don't.
 | 
						|
	// In order not to count the same child multiple times, we need to maintain a separate set of visited children,
 | 
						|
	// which is basically the same as what GetAllChildren() does. So, we're using it here!
 | 
						|
	return GetAllChildren().size();
 | 
						|
}
 | 
						|
 | 
						|
std::set<Checkable::Ptr> Checkable::GetAllChildren() const
 | 
						|
{
 | 
						|
	std::set<Checkable::Ptr> children = GetChildren();
 | 
						|
 | 
						|
	GetAllChildrenInternal(children, 0);
 | 
						|
 | 
						|
	return children;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Retrieve all direct and indirect children of the current Checkable.
 | 
						|
 *
 | 
						|
 * Note, this function performs a recursive call chain traversing all the children of the current Checkable
 | 
						|
 * up to a certain limit (256). When that limit is reached, it will log a warning message and abort the operation.
 | 
						|
 *
 | 
						|
 * @param children - The set of children to be filled with all the children of the current Checkable.
 | 
						|
 * @param level - The current level of recursion.
 | 
						|
 */
 | 
						|
void Checkable::GetAllChildrenInternal(std::set<Checkable::Ptr>& children, int level) const
 | 
						|
{
 | 
						|
	if (level > l_MaxDependencyRecursionLevel) {
 | 
						|
		Log(LogWarning, "Checkable")
 | 
						|
			<< "Too many nested dependencies (>" << l_MaxDependencyRecursionLevel << ") for checkable '" << GetName() << "': aborting traversal.";
 | 
						|
		return ;
 | 
						|
	}
 | 
						|
 | 
						|
	std::set<Checkable::Ptr> localChildren;
 | 
						|
 | 
						|
	for (const Checkable::Ptr& checkable : children) {
 | 
						|
		if (auto cChildren(checkable->GetChildren()); !cChildren.empty()) {
 | 
						|
			GetAllChildrenInternal(cChildren, level + 1);
 | 
						|
			localChildren.insert(cChildren.begin(), cChildren.end());
 | 
						|
		}
 | 
						|
 | 
						|
		if (level != 0) { // Recursion level 0 is the initiator, so checkable is already in the set.
 | 
						|
			localChildren.insert(checkable);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	children.insert(localChildren.begin(), localChildren.end());
 | 
						|
}
 |