|
| 1 | +/* Icinga 2 | (c) 2025 Icinga GmbH | GPLv2+ */ |
| 2 | + |
| 3 | +#include "icinga/dependency.hpp" |
| 4 | +#include "icinga/host.hpp" |
| 5 | +#include "icinga/service.hpp" |
| 6 | + |
| 7 | +using namespace icinga; |
| 8 | + |
| 9 | +/** |
| 10 | + * Construct a helper for evaluating the state of dependencies. |
| 11 | + * |
| 12 | + * @param dt Dependency type to check for within the individual methods. |
| 13 | + */ |
| 14 | +DependencyStateChecker::DependencyStateChecker(DependencyType dt) |
| 15 | + : m_DependencyType(dt) |
| 16 | +{ |
| 17 | +} |
| 18 | + |
| 19 | +/** |
| 20 | + * Checks whether a given checkable is currently reachable. |
| 21 | + * |
| 22 | + * @param checkable The checkable to check reachability for. |
| 23 | + * @param rstack The recursion stack level to prevent infinite recursion, defaults to 0. |
| 24 | + * @return Whether the given checkable is reachable. |
| 25 | + */ |
| 26 | +bool DependencyStateChecker::IsReachable(Checkable::ConstPtr checkable, int rstack) |
| 27 | +{ |
| 28 | + if (rstack > Dependency::MaxDependencyRecursionLevel) { |
| 29 | + Log(LogWarning, "Checkable") |
| 30 | + << "Too many nested dependencies (>" << Dependency::MaxDependencyRecursionLevel << ") for checkable '" |
| 31 | + << checkable->GetName() << "': Dependency failed."; |
| 32 | + |
| 33 | + return false; |
| 34 | + } |
| 35 | + |
| 36 | + /* implicit dependency on host if this is a service */ |
| 37 | + const auto* service = dynamic_cast<const Service*>(checkable.get()); |
| 38 | + if (service && (m_DependencyType == DependencyState || m_DependencyType == DependencyNotification)) { |
| 39 | + Host::Ptr host = service->GetHost(); |
| 40 | + |
| 41 | + if (host && host->GetState() != HostUp && host->GetStateType() == StateTypeHard) { |
| 42 | + return false; |
| 43 | + } |
| 44 | + } |
| 45 | + |
| 46 | + for (auto& dependencyGroup : checkable->GetDependencyGroups()) { |
| 47 | + if (auto state(GetState(dependencyGroup, checkable.get(), rstack + 1)); state != DependencyGroup::State::Ok) { |
| 48 | + Log(LogDebug, "Checkable") |
| 49 | + << "Dependency group '" << dependencyGroup->GetRedundancyGroupName() << "' have failed for checkable '" |
| 50 | + << checkable->GetName() << "': Marking as unreachable."; |
| 51 | + |
| 52 | + return false; |
| 53 | + } |
| 54 | + } |
| 55 | + |
| 56 | + return true; |
| 57 | +} |
| 58 | + |
| 59 | +/** |
| 60 | + * Retrieve the state of the given dependency group. |
| 61 | + * |
| 62 | + * The state of the dependency group is determined based on the state of the parent Checkables and dependency objects |
| 63 | + * of the group. A dependency group is considered unreachable when none of the parent Checkables is reachable. However, |
| 64 | + * a dependency group may still be marked as failed even when it has reachable parent Checkables, but an unreachable |
| 65 | + * group has always a failed state. |
| 66 | + * |
| 67 | + * @param group |
| 68 | + * @param child The child Checkable to evaluate the state for. |
| 69 | + * @param rstack The recursion stack level to prevent infinite recursion, defaults to 0. |
| 70 | + * |
| 71 | + * @return - Returns the state of the current dependency group. |
| 72 | + */ |
| 73 | +DependencyGroup::State DependencyStateChecker::GetState(const DependencyGroup::ConstPtr& group, const Checkable* child, int rstack) |
| 74 | +{ |
| 75 | + using State = DependencyGroup::State; |
| 76 | + |
| 77 | + auto dependencies(group->GetDependenciesForChild(child)); |
| 78 | + size_t reachable = 0, available = 0; |
| 79 | + |
| 80 | + for (const auto& dependency : dependencies) { |
| 81 | + if (IsReachable(dependency->GetParent(), rstack)) { |
| 82 | + reachable++; |
| 83 | + |
| 84 | + // Only reachable parents are considered for availability. If they are unreachable and checks are |
| 85 | + // disabled, they could be incorrectly treated as available otherwise. |
| 86 | + if (dependency->IsAvailable(m_DependencyType)) { |
| 87 | + available++; |
| 88 | + } |
| 89 | + } |
| 90 | + } |
| 91 | + |
| 92 | + if (group->IsRedundancyGroup()) { |
| 93 | + // The state of a redundancy group is determined by the best state of any parent. If any parent ist reachable, |
| 94 | + // the redundancy group is reachable, analogously for availability. |
| 95 | + if (reachable == 0) { |
| 96 | + return State::Unreachable; |
| 97 | + } else if (available == 0) { |
| 98 | + return State::Failed; |
| 99 | + } else { |
| 100 | + return State::Ok; |
| 101 | + } |
| 102 | + } else { |
| 103 | + // For dependencies without a redundancy group, dependencies.size() will be 1 in almost all cases. It will only |
| 104 | + // contain more elements if there are duplicate dependency config objects between two checkables. In this case, |
| 105 | + // all of them have to be reachable/available as they don't provide redundancy. |
| 106 | + if (reachable < dependencies.size()) { |
| 107 | + return State::Unreachable; |
| 108 | + } else if (available < dependencies.size()) { |
| 109 | + return State::Failed; |
| 110 | + } else { |
| 111 | + return State::Ok; |
| 112 | + } |
| 113 | + } |
| 114 | +} |
0 commit comments