icinga2/lib/icinga/checkable-dependency.cpp
Edgar Fuß cfef9fdadc Introduce redundancy groups for Dependency Objects
Traditional behaviour was to regard all dependecies as cumulative (e.g., the parent considered unreachable if any one dependency is violated), commit ed58922389 made all dependencies regarded redundant (e.g., the parent considered unreachable only if all dependency are violated). This may lead to unrelated services (or even hosts vs. services) inadvertantly regarded to be redundant to each other.

Most importantly, applying the explicit "disable-host-service-checks" dependency described in the "Monitoring Basics" chapter will defeat all other dependencies.

This commit introduces a new "redundancy_group" attribute for dependencies.
Specifying a redundancy_group causes a dependency to be regarded as redundant only inside that redundancy group.
Dependencies lacking a redundancy_group attribute are regarded as essential for the parent.

This allows for both cumulative and redundant dependencies and even a combination (cumulation of redundancies, like SSH depeding on both LDAP and DNS to function, while operating redundant LDAP servers as well as redundant DNS resolvers).

This commit lacks changes to the tests.
2023-02-21 16:23:36 +01:00

176 lines
4.9 KiB
C++

/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
#include "icinga/service.hpp"
#include "icinga/dependency.hpp"
#include "base/logger.hpp"
using namespace icinga;
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
{
/* Anything greater than 256 causes recursion bus errors. */
int limit = 256;
if (rstack > limit) {
Log(LogWarning, "Checkable")
<< "Too many nested dependencies (>" << limit << ") 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)
if (violated.find(redundancy_group) == violated.end())
violated.insert(std::make_pair(redundancy_group, dep));
} else if (!redundancy_group.empty()) {
// definitely mark this dependency group as passed
violated.insert(std::make_pair(redundancy_group, nullptr));
}
}
auto violator = std::find_if(violated.begin(), violated.end(), [](const std::pair<std::string, Dependency::Ptr>&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;
}
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;
}
std::set<Checkable::Ptr> Checkable::GetAllChildren() const
{
std::set<Checkable::Ptr> children = GetChildren();
GetAllChildrenInternal(children, 0);
return children;
}
void Checkable::GetAllChildrenInternal(std::set<Checkable::Ptr>& children, int level) const
{
if (level > 32)
return;
std::set<Checkable::Ptr> localChildren;
for (const Checkable::Ptr& checkable : children) {
std::set<Checkable::Ptr> cChildren = checkable->GetChildren();
if (!cChildren.empty()) {
GetAllChildrenInternal(cChildren, level + 1);
localChildren.insert(cChildren.begin(), cChildren.end());
}
localChildren.insert(checkable);
}
children.insert(localChildren.begin(), localChildren.end());
}