icinga2/lib/icinga/dependency.hpp
Julian Brost 065118bc22 Make DependencyGroup::State an enum
The previous struct used two bools to represent three useful states. Make this
more explicit by having these three states as an enum.
2025-03-19 16:28:00 +01:00

228 lines
9.2 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Icinga 2 | (c) 2012 Icinga GmbH | GPLv2+ */
#ifndef DEPENDENCY_H
#define DEPENDENCY_H
#include "base/shared-object.hpp"
#include "config/configitem.hpp"
#include "icinga/i2-icinga.hpp"
#include "icinga/dependency-ti.hpp"
#include "icinga/timeperiod.hpp"
#include <map>
#include <tuple>
#include <unordered_map>
namespace icinga
{
class ApplyRule;
struct ScriptFrame;
class Host;
class Service;
/**
* A service dependency..
*
* @ingroup icinga
*/
class Dependency final : public ObjectImpl<Dependency>
{
public:
static void StaticInitialize();
DECLARE_OBJECT(Dependency);
DECLARE_OBJECTNAME(Dependency);
intrusive_ptr<Checkable> GetParent() const;
intrusive_ptr<Checkable> GetChild() const;
TimePeriod::Ptr GetPeriod() const;
bool IsAvailable(DependencyType dt) const;
void ValidateStates(const Lazy<Array::Ptr>& lvalue, const ValidationUtils& utils) override;
static void EvaluateApplyRules(const intrusive_ptr<Host>& host);
static void EvaluateApplyRules(const intrusive_ptr<Service>& service);
/* Note: Only use them for unit test mocks. Prefer InitChildParentReferences(). */
void SetParent(intrusive_ptr<Checkable> parent);
void SetChild(intrusive_ptr<Checkable> child);
protected:
void OnConfigLoaded() override;
void OnAllConfigLoaded() override;
void Stop(bool runtimeRemoved) override;
void InitChildParentReferences();
private:
Checkable::Ptr m_Parent;
Checkable::Ptr m_Child;
static bool EvaluateApplyRuleInstance(const Checkable::Ptr& checkable, const String& name, ScriptFrame& frame, const ApplyRule& rule, bool skipFilter);
static bool EvaluateApplyRule(const Checkable::Ptr& checkable, const ApplyRule& rule, bool skipFilter = false);
static void BeforeOnAllConfigLoadedHandler(const ConfigItems& items);
};
/**
* A DependencyGroup represents a set of dependencies that are somehow related to each other.
*
* Specifically, a DependencyGroup is a container for Dependency objects of different Checkables that share the same
* child -> parent relationship config, thus forming a group of dependencies. All dependencies of a Checkable that
* have the same "redundancy_group" attribute value set are guaranteed to be part of the same DependencyGroup object,
* and another Checkable will join that group if and only if it has identical set of dependencies, that is, the same
* parent(s), same redundancy group name and all other dependency attributes required to form a composite key.
*
* More specifically, let's say we have a dependency graph like this:
* @verbatim
* PP1 PP2
* /\ /\
* || ||
* ||||
* P1 - ( "RG1" ) - P2
*
* /\ /\
* || ||
* C1 C2
* @endverbatim
* The arrows represent a dependency relationship from bottom to top, i.e. both "C1" and "C2" depend on
* their "RG1" redundancy group, and "P1" and "P2" depend each on their respective parents (PP1, PP2 - no group).
* Now, as one can see, both "C1" and "C2" have identical dependencies, that is, they both depend on the same
* redundancy group "RG1" (these could e.g. be constructed through some Apply Rules).
*
* So, instead of having to maintain two separate copies of that graph, we can bring that imaginary redundancy group
* into reality by putting both "P1" and "P2" into an actual DependencyGroup object. However, we don't really put "P1"
* and "P2" objects into that group, but rather the actual Dependency objects of both child Checkables. Therefore, the
* group wouldn't just contain 2 dependencies, but 4 in total, i.e. 2 for each child Checkable (C1 -> {P1, P2} and
* C2 -> {P1, P2}). This way, both child Checkables can just refer to that very same DependencyGroup object.
*
* However, since not all dependencies are part of a redundancy group, we also have to consider the case where
* a Checkable has dependencies that are not part of any redundancy group, like P1 -> PP1. In such situations,
* each of the child Checkables (e.g. P1, P2) will have their own (sharable) DependencyGroup object just like for RGs.
* This allows us to keep the implementation simple and treat redundant and non-redundant dependencies in the same
* way, without having to introduce any special cases everywhere. So, in the end, we'd have 3 dependency groups in
* total, i.e. one for the redundancy group "RG1" (shared by C1 and C2), and two distinct groups for P1 and P2.
*
* @ingroup icinga
*/
class DependencyGroup final : public SharedObject
{
public:
DECLARE_PTR_TYPEDEFS(DependencyGroup);
/**
* Defines the key type of each dependency group members.
*
* This tuple consists of the dependency parent Checkable, the dependency time period (nullptr if not configured),
* the state filter, and the ignore soft states flag. Each of these values influences the availability of the
* dependency object, and thus used to group similar dependencies from different Checkables together.
*/
using CompositeKeyType = std::tuple<Checkable*, TimePeriod*, int, bool>;
/**
* Represents the value type of each dependency group members.
*
* It stores the dependency objects of any given Checkable that produce the same composite key (CompositeKeyType).
* In other words, when looking at the dependency graph from the class description, the two dependency objects
* {C1, C2} -> P1 produce the same composite key, thus they are mapped to the same MemberValueType container with
* "C1" and "C2" as their keys respectively. Since Icinga 2 allows to construct different identical dependencies
* (duplicates), we're using a multimap instead of a simple map here.
*/
using MemberValueType = std::unordered_multimap<const Checkable*, Dependency*>;
using MembersMap = std::map<CompositeKeyType, MemberValueType>;
DependencyGroup(String name, const std::set<Dependency::Ptr>& dependencies);
static DependencyGroup::Ptr Register(const DependencyGroup::Ptr& dependencyGroup);
static std::pair<std::set<Dependency::Ptr>, bool> Unregister(const DependencyGroup::Ptr& dependencyGroup, const Checkable::Ptr& child);
static size_t GetRegistrySize();
static CompositeKeyType MakeCompositeKeyFor(const Dependency::Ptr& dependency);
/**
* Check whether the current dependency group represents an explicitly configured redundancy group.
*
* @return bool - Returns true if it's a redundancy group, false otherwise.
*/
inline bool IsRedundancyGroup() const
{
return !m_RedundancyGroupName.IsEmpty();
}
bool HasChildren() const;
void AddDependency(const Dependency::Ptr& dependency);
void RemoveDependency(const Dependency::Ptr& dependency);
std::vector<Dependency::Ptr> GetDependenciesForChild(const Checkable* child) const;
void LoadParents(std::set<Checkable::Ptr>& parents) const;
size_t GetDependenciesCount() const;
void SetIcingaDBIdentifier(const String& identifier);
String GetIcingaDBIdentifier() const;
const String& GetRedundancyGroupName() const;
String GetCompositeKey();
enum class State { Ok, Failed, Unreachable };
State GetState(const Checkable* child, DependencyType dt = DependencyState, int rstack = 0) const;
static boost::signals2::signal<void(const Checkable::Ptr&, const DependencyGroup::Ptr&)> OnChildRegistered;
static boost::signals2::signal<void(const DependencyGroup::Ptr&, const std::vector<Dependency::Ptr>&, bool)> OnChildRemoved;
private:
void CopyDependenciesTo(const DependencyGroup::Ptr& dest);
struct Hash
{
size_t operator()(const DependencyGroup::Ptr& dependencyGroup) const
{
size_t hash = std::hash<String>{}(dependencyGroup->GetRedundancyGroupName());
for (const auto& [key, group] : dependencyGroup->m_Members) {
boost::hash_combine(hash, key);
}
return hash;
}
};
struct Equal
{
bool operator()(const DependencyGroup::Ptr& lhs, const DependencyGroup::Ptr& rhs) const
{
if (lhs->GetRedundancyGroupName() != rhs->GetRedundancyGroupName()) {
return false;
}
return std::equal(
lhs->m_Members.begin(), lhs->m_Members.end(),
rhs->m_Members.begin(), rhs->m_Members.end(),
[](const auto& l, const auto& r) { return l.first == r.first; }
);
}
};
private:
mutable std::mutex m_Mutex;
/**
* This identifier is used by Icinga DB to cache the unique hash of this dependency group.
*
* For redundancy groups, once Icinga DB sets this identifier, it will never change again for the lifetime
* of the object. For non-redundant dependency groups, this identifier is (mis)used to cache the shared edge
* state ID of the group. Specifically, non-redundant dependency groups are irrelevant for Icinga DB, so since
* this field isn't going to be used for anything else, we use it to cache the computed shared edge state ID.
* Likewise, if that gets set, it will never change again for the lifetime of the object as well.
*/
String m_IcingaDBIdentifier;
String m_RedundancyGroupName;
MembersMap m_Members;
using RegistryType = std::unordered_set<DependencyGroup::Ptr, Hash, Equal>;
// The global registry of dependency groups.
static std::mutex m_RegistryMutex;
static RegistryType m_Registry;
};
}
#endif /* DEPENDENCY_H */