mirror of
https://github.com/Icinga/icinga2.git
synced 2025-04-08 17:05:25 +02:00
Merge pull request #10290 from Icinga/icingadb-dependencies-sync
Sync dependencies to Redis
This commit is contained in:
commit
5a6b2044b1
@ -39,7 +39,7 @@ set(icinga_SOURCES
|
||||
comment.cpp comment.hpp comment-ti.hpp
|
||||
compatutility.cpp compatutility.hpp
|
||||
customvarobject.cpp customvarobject.hpp customvarobject-ti.hpp
|
||||
dependency.cpp dependency.hpp dependency-ti.hpp dependency-apply.cpp
|
||||
dependency.cpp dependency-group.cpp dependency.hpp dependency-ti.hpp dependency-apply.cpp
|
||||
downtime.cpp downtime.hpp downtime-ti.hpp
|
||||
envresolver.cpp envresolver.hpp
|
||||
eventcommand.cpp eventcommand.hpp eventcommand-ti.hpp
|
||||
|
@ -154,6 +154,10 @@ Checkable::ProcessingResult Checkable::ProcessCheckResult(const CheckResult::Ptr
|
||||
bool reachable = IsReachable();
|
||||
bool notification_reachable = IsReachable(DependencyNotification);
|
||||
|
||||
// Cache whether the previous state of this Checkable affects its children before overwriting the last check result.
|
||||
// This will be used to determine whether the on reachability changed event should be triggered.
|
||||
bool affectsPreviousStateChildren(reachable && AffectsChildren());
|
||||
|
||||
ObjectLock olock(this);
|
||||
|
||||
CheckResult::Ptr old_cr = GetLastCheckResult();
|
||||
@ -533,7 +537,7 @@ Checkable::ProcessingResult Checkable::ProcessCheckResult(const CheckResult::Ptr
|
||||
}
|
||||
|
||||
/* update reachability for child objects */
|
||||
if ((stateChange || hardChange) && !children.empty())
|
||||
if ((stateChange || hardChange) && !children.empty() && (affectsPreviousStateChildren || AffectsChildren()))
|
||||
OnReachabilityChanged(this, cr, children, origin);
|
||||
|
||||
return Result::Ok;
|
||||
|
@ -3,26 +3,169 @@
|
||||
#include "icinga/service.hpp"
|
||||
#include "icinga/dependency.hpp"
|
||||
#include "base/logger.hpp"
|
||||
#include <unordered_map>
|
||||
|
||||
using namespace icinga;
|
||||
|
||||
void Checkable::AddDependency(const Dependency::Ptr& dep)
|
||||
/**
|
||||
* The maximum number of allowed dependency recursion levels.
|
||||
*
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Register all the dependency groups of the current Checkable to the global dependency group registry.
|
||||
*
|
||||
* Initially, each Checkable object tracks locally its own dependency groups on Icinga 2 startup, and once the start
|
||||
* signal of that Checkable is emitted, it pushes all the local tracked dependency groups to the global registry.
|
||||
* Once the global registry is populated with all the local dependency groups, this Checkable may not necessarily
|
||||
* contain the exact same dependency groups as it did before, as identical groups are merged together in the registry,
|
||||
* but it's guaranteed to have the same *number* of dependency groups as before.
|
||||
*/
|
||||
void Checkable::PushDependencyGroupsToRegistry()
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_DependencyMutex);
|
||||
m_Dependencies.insert(dep);
|
||||
std::lock_guard lock(m_DependencyMutex);
|
||||
if (m_PendingDependencies != nullptr) {
|
||||
for (const auto& [key, dependencies] : *m_PendingDependencies) {
|
||||
String redundancyGroup = std::holds_alternative<String>(key) ? std::get<String>(key) : "";
|
||||
m_DependencyGroups.emplace(key, DependencyGroup::Register(new DependencyGroup(redundancyGroup, dependencies)));
|
||||
}
|
||||
m_PendingDependencies.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void Checkable::RemoveDependency(const Dependency::Ptr& dep)
|
||||
std::vector<DependencyGroup::Ptr> Checkable::GetDependencyGroups() const
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_DependencyMutex);
|
||||
m_Dependencies.erase(dep);
|
||||
std::lock_guard lock(m_DependencyMutex);
|
||||
|
||||
std::vector<DependencyGroup::Ptr> dependencyGroups;
|
||||
for (const auto& [_, dependencyGroup] : m_DependencyGroups) {
|
||||
dependencyGroups.emplace_back(dependencyGroup);
|
||||
}
|
||||
return dependencyGroups;
|
||||
}
|
||||
|
||||
std::vector<Dependency::Ptr> Checkable::GetDependencies() const
|
||||
/**
|
||||
* Get the key for the provided dependency group.
|
||||
*
|
||||
* The key is either the parent Checkable object or the redundancy group name of the dependency object.
|
||||
* This is used to uniquely identify the dependency group within a given Checkable object.
|
||||
*
|
||||
* @param dependency The dependency to get the key for.
|
||||
*
|
||||
* @return - Returns the key for the provided dependency group.
|
||||
*/
|
||||
static std::variant<Checkable*, String> GetDependencyGroupKey(const Dependency::Ptr& dependency)
|
||||
{
|
||||
if (auto redundancyGroup(dependency->GetRedundancyGroup()); !redundancyGroup.IsEmpty()) {
|
||||
return std::move(redundancyGroup);
|
||||
}
|
||||
|
||||
return dependency->GetParent().get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the provided dependency to the current Checkable list of dependencies.
|
||||
*
|
||||
* @param dependency The dependency to add.
|
||||
*/
|
||||
void Checkable::AddDependency(const Dependency::Ptr& dependency)
|
||||
{
|
||||
std::unique_lock lock(m_DependencyMutex);
|
||||
|
||||
auto dependencyGroupKey(GetDependencyGroupKey(dependency));
|
||||
if (m_PendingDependencies != nullptr) {
|
||||
(*m_PendingDependencies)[dependencyGroupKey].emplace(dependency);
|
||||
return;
|
||||
}
|
||||
|
||||
std::set<Dependency::Ptr> dependencies;
|
||||
bool removeGroup(false);
|
||||
|
||||
DependencyGroup::Ptr existingGroup;
|
||||
if (auto it(m_DependencyGroups.find(dependencyGroupKey)); it != m_DependencyGroups.end()) {
|
||||
existingGroup = it->second;
|
||||
std::tie(dependencies, removeGroup) = DependencyGroup::Unregister(existingGroup, this);
|
||||
m_DependencyGroups.erase(it);
|
||||
}
|
||||
|
||||
dependencies.emplace(dependency);
|
||||
|
||||
auto dependencyGroup(DependencyGroup::Register(new DependencyGroup(dependency->GetRedundancyGroup(), dependencies)));
|
||||
m_DependencyGroups.emplace(dependencyGroupKey, dependencyGroup);
|
||||
|
||||
lock.unlock();
|
||||
|
||||
if (existingGroup) {
|
||||
dependencies.erase(dependency);
|
||||
DependencyGroup::OnChildRemoved(existingGroup, {dependencies.begin(), dependencies.end()}, removeGroup);
|
||||
}
|
||||
DependencyGroup::OnChildRegistered(this, dependencyGroup);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the provided dependency from the current Checkable list of dependencies.
|
||||
*
|
||||
* @param dependency The dependency to remove.
|
||||
* @param runtimeRemoved Whether the given dependency object is being removed at runtime.
|
||||
*/
|
||||
void Checkable::RemoveDependency(const Dependency::Ptr& dependency, bool runtimeRemoved)
|
||||
{
|
||||
std::unique_lock lock(m_DependencyMutex);
|
||||
|
||||
auto dependencyGroupKey(GetDependencyGroupKey(dependency));
|
||||
auto it = m_DependencyGroups.find(dependencyGroupKey);
|
||||
if (it == m_DependencyGroups.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DependencyGroup::Ptr existingGroup(it->second);
|
||||
auto [dependencies, removeGroup] = DependencyGroup::Unregister(existingGroup, this);
|
||||
|
||||
m_DependencyGroups.erase(it);
|
||||
dependencies.erase(dependency);
|
||||
|
||||
DependencyGroup::Ptr newDependencyGroup;
|
||||
if (!dependencies.empty()) {
|
||||
newDependencyGroup = DependencyGroup::Register(new DependencyGroup(dependency->GetRedundancyGroup(), dependencies));
|
||||
m_DependencyGroups.emplace(dependencyGroupKey, newDependencyGroup);
|
||||
}
|
||||
|
||||
lock.unlock();
|
||||
|
||||
if (runtimeRemoved) {
|
||||
dependencies.emplace(dependency);
|
||||
DependencyGroup::OnChildRemoved(existingGroup, {dependencies.begin(), dependencies.end()}, removeGroup);
|
||||
|
||||
if (newDependencyGroup) {
|
||||
DependencyGroup::OnChildRegistered(this, newDependencyGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<Dependency::Ptr> Checkable::GetDependencies(bool includePending) const
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(m_DependencyMutex);
|
||||
return std::vector<Dependency::Ptr>(m_Dependencies.begin(), m_Dependencies.end());
|
||||
std::vector<Dependency::Ptr> dependencies;
|
||||
|
||||
if (includePending && m_PendingDependencies != nullptr) {
|
||||
for (const auto& [group, groupDeps] : *m_PendingDependencies) {
|
||||
dependencies.insert(dependencies.end(), groupDeps.begin(), groupDeps.end());
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& [_, dependencyGroup] : m_DependencyGroups) {
|
||||
auto tmpDependencies(dependencyGroup->GetDependenciesForChild(this));
|
||||
dependencies.insert(dependencies.end(), tmpDependencies.begin(), tmpDependencies.end());
|
||||
}
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
bool Checkable::HasAnyDependencies() const
|
||||
{
|
||||
std::unique_lock lock(m_DependencyMutex);
|
||||
return !m_DependencyGroups.empty() || !m_ReverseDependencies.empty();
|
||||
}
|
||||
|
||||
void Checkable::AddReverseDependency(const Dependency::Ptr& dep)
|
||||
@ -43,89 +186,72 @@ std::vector<Dependency::Ptr> Checkable::GetReverseDependencies() const
|
||||
return std::vector<Dependency::Ptr>(m_ReverseDependencies.begin(), m_ReverseDependencies.end());
|
||||
}
|
||||
|
||||
bool Checkable::IsReachable(DependencyType dt, Dependency::Ptr *failedDependency, int rstack) const
|
||||
bool Checkable::IsReachable(DependencyType dt, int rstack) const
|
||||
{
|
||||
/* Anything greater than 256 causes recursion bus errors. */
|
||||
int limit = 256;
|
||||
|
||||
if (rstack > limit) {
|
||||
if (rstack > l_MaxDependencyRecursionLevel) {
|
||||
Log(LogWarning, "Checkable")
|
||||
<< "Too many nested dependencies (>" << limit << ") for checkable '" << GetName() << "': Dependency failed.";
|
||||
<< "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;
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& dependencyGroup : GetDependencyGroups()) {
|
||||
if (auto state(dependencyGroup->GetState(this, dt, rstack + 1)); state != DependencyGroup::State::Ok) {
|
||||
Log(LogDebug, "Checkable")
|
||||
<< "Dependency group '" << dependencyGroup->GetRedundancyGroupName() << "' have failed for checkable '"
|
||||
<< GetName() << "': Marking as unreachable.";
|
||||
|
||||
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 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;
|
||||
}
|
||||
|
||||
if (failedDependency)
|
||||
*failedDependency = nullptr;
|
||||
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 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);
|
||||
for (auto& dependencyGroup : GetDependencyGroups()) {
|
||||
dependencyGroup->LoadParents(parents);
|
||||
}
|
||||
|
||||
return parents;
|
||||
@ -145,32 +271,51 @@ std::set<Checkable::Ptr> Checkable::GetChildren() const
|
||||
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();
|
||||
std::set<Checkable::Ptr> children;
|
||||
|
||||
GetAllChildrenInternal(children, 0);
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
void Checkable::GetAllChildrenInternal(std::set<Checkable::Ptr>& children, int level) const
|
||||
/**
|
||||
* 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 seenChildren - A container to store all the traversed children into.
|
||||
* @param level - The current level of recursion.
|
||||
*/
|
||||
void Checkable::GetAllChildrenInternal(std::set<Checkable::Ptr>& seenChildren, int level) const
|
||||
{
|
||||
if (level > 32)
|
||||
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) {
|
||||
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());
|
||||
for (const Checkable::Ptr& checkable : GetChildren()) {
|
||||
if (auto [_, inserted] = seenChildren.insert(checkable); inserted) {
|
||||
checkable->GetAllChildrenInternal(seenChildren, level + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -167,8 +167,7 @@ void Checkable::FireSuppressedNotifications()
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& dep : GetDependencies()) {
|
||||
auto parent (dep->GetParent());
|
||||
for (auto& parent : GetParents()) {
|
||||
ObjectLock oLock (parent);
|
||||
|
||||
if (!parent->GetProblem() && parent->GetLastStateChange() >= threshold) {
|
||||
|
@ -80,6 +80,8 @@ void Checkable::OnAllConfigLoaded()
|
||||
|
||||
void Checkable::Start(bool runtimeCreated)
|
||||
{
|
||||
PushDependencyGroupsToRegistry();
|
||||
|
||||
double now = Utility::GetTime();
|
||||
|
||||
{
|
||||
|
@ -18,6 +18,7 @@
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <limits>
|
||||
#include <variant>
|
||||
|
||||
namespace icinga
|
||||
{
|
||||
@ -57,6 +58,7 @@ enum FlappingStateFilter
|
||||
class CheckCommand;
|
||||
class EventCommand;
|
||||
class Dependency;
|
||||
class DependencyGroup;
|
||||
|
||||
/**
|
||||
* An Icinga service.
|
||||
@ -77,10 +79,12 @@ public:
|
||||
std::set<Checkable::Ptr> GetParents() const;
|
||||
std::set<Checkable::Ptr> GetChildren() const;
|
||||
std::set<Checkable::Ptr> GetAllChildren() const;
|
||||
size_t GetAllChildrenCount() const;
|
||||
|
||||
void AddGroup(const String& name);
|
||||
|
||||
bool IsReachable(DependencyType dt = DependencyState, intrusive_ptr<Dependency> *failedDependency = nullptr, int rstack = 0) const;
|
||||
bool IsReachable(DependencyType dt = DependencyState, int rstack = 0) const;
|
||||
bool AffectsChildren() const;
|
||||
|
||||
AcknowledgementType GetAcknowledgement();
|
||||
|
||||
@ -182,9 +186,12 @@ public:
|
||||
bool IsFlapping() const;
|
||||
|
||||
/* Dependencies */
|
||||
void AddDependency(const intrusive_ptr<Dependency>& dep);
|
||||
void RemoveDependency(const intrusive_ptr<Dependency>& dep);
|
||||
std::vector<intrusive_ptr<Dependency> > GetDependencies() const;
|
||||
void PushDependencyGroupsToRegistry();
|
||||
std::vector<intrusive_ptr<DependencyGroup>> GetDependencyGroups() const;
|
||||
void AddDependency(const intrusive_ptr<Dependency>& dependency);
|
||||
void RemoveDependency(const intrusive_ptr<Dependency>& dependency, bool runtimeRemoved = false);
|
||||
std::vector<intrusive_ptr<Dependency> > GetDependencies(bool includePending = false) const;
|
||||
bool HasAnyDependencies() const;
|
||||
|
||||
void AddReverseDependency(const intrusive_ptr<Dependency>& dep);
|
||||
void RemoveReverseDependency(const intrusive_ptr<Dependency>& dep);
|
||||
@ -244,10 +251,21 @@ private:
|
||||
|
||||
/* Dependencies */
|
||||
mutable std::mutex m_DependencyMutex;
|
||||
std::set<intrusive_ptr<Dependency> > m_Dependencies;
|
||||
std::map<std::variant<Checkable*, String>, intrusive_ptr<DependencyGroup>> m_DependencyGroups;
|
||||
std::set<intrusive_ptr<Dependency> > m_ReverseDependencies;
|
||||
/**
|
||||
* Registering a checkable to its parent DependencyGroups is delayed during config loading until all dependencies
|
||||
* were registered on the checkable. m_PendingDependencies is used to temporarily store the dependencies until then.
|
||||
* It is a pointer type for two reasons:
|
||||
* 1. The field is no longer needed after the DependencyGroups were registered, having it as a pointer reduces the
|
||||
* overhead from sizeof(std::map<>) to sizeof(std::map<>*).
|
||||
* 2. It allows the field to also be used as a flag: the delayed group registration is only done until it is reset
|
||||
* to nullptr.
|
||||
*/
|
||||
std::unique_ptr<std::map<std::variant<Checkable*, String>, std::set<intrusive_ptr<Dependency>>>>
|
||||
m_PendingDependencies {std::make_unique<decltype(m_PendingDependencies)::element_type>()};
|
||||
|
||||
void GetAllChildrenInternal(std::set<Checkable::Ptr>& children, int level = 0) const;
|
||||
void GetAllChildrenInternal(std::set<Checkable::Ptr>& seenChildren, int level = 0) const;
|
||||
|
||||
/* Flapping */
|
||||
static const std::map<String, int> m_FlappingStateFilterMap;
|
||||
|
348
lib/icinga/dependency-group.cpp
Normal file
348
lib/icinga/dependency-group.cpp
Normal file
@ -0,0 +1,348 @@
|
||||
/* Icinga 2 | (c) 2024 Icinga GmbH | GPLv2+ */
|
||||
|
||||
#include "icinga/dependency.hpp"
|
||||
#include "base/object-packer.hpp"
|
||||
|
||||
using namespace icinga;
|
||||
|
||||
boost::signals2::signal<void(const Checkable::Ptr&, const DependencyGroup::Ptr&)> DependencyGroup::OnChildRegistered;
|
||||
boost::signals2::signal<void(const DependencyGroup::Ptr&, const std::vector<Dependency::Ptr>&, bool)> DependencyGroup::OnChildRemoved;
|
||||
|
||||
std::mutex DependencyGroup::m_RegistryMutex;
|
||||
DependencyGroup::RegistryType DependencyGroup::m_Registry;
|
||||
|
||||
/**
|
||||
* Register the provided dependency group to the global dependency group registry.
|
||||
*
|
||||
* In case there is already an identical dependency group in the registry, the provided dependency group is merged
|
||||
* with the existing one, and that group is returned. Otherwise, the provided dependency group is registered as is,
|
||||
* and it's returned.
|
||||
*
|
||||
* @param dependencyGroup The dependency group to register.
|
||||
*/
|
||||
DependencyGroup::Ptr DependencyGroup::Register(const DependencyGroup::Ptr& dependencyGroup)
|
||||
{
|
||||
std::lock_guard lock(m_RegistryMutex);
|
||||
if (auto [it, inserted] = m_Registry.insert(dependencyGroup); !inserted) {
|
||||
dependencyGroup->CopyDependenciesTo(*it);
|
||||
return *it;
|
||||
}
|
||||
return dependencyGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detach the provided child Checkable from the specified dependency group.
|
||||
*
|
||||
* Unregisters all the dependency objects the child Checkable depends on from the provided dependency group and
|
||||
* removes the dependency group from the global registry if it becomes empty afterward.
|
||||
*
|
||||
* @param dependencyGroup The dependency group to unregister the child Checkable from.
|
||||
* @param child The child Checkable to detach from the dependency group.
|
||||
*
|
||||
* @return - Returns the dependency objects of the child Checkable that were member of the provided dependency group
|
||||
* and a boolean indicating whether the dependency group has been erased from the global registry.
|
||||
*/
|
||||
std::pair<std::set<Dependency::Ptr>, bool> DependencyGroup::Unregister(const DependencyGroup::Ptr& dependencyGroup, const Checkable::Ptr& child)
|
||||
{
|
||||
std::lock_guard lock(m_RegistryMutex);
|
||||
if (auto it(m_Registry.find(dependencyGroup)); it != m_Registry.end()) {
|
||||
auto& existingGroup(*it);
|
||||
auto dependencies(existingGroup->GetDependenciesForChild(child.get()));
|
||||
|
||||
for (const auto& dependency : dependencies) {
|
||||
existingGroup->RemoveDependency(dependency);
|
||||
}
|
||||
|
||||
bool remove = !existingGroup->HasChildren();
|
||||
if (remove) {
|
||||
m_Registry.erase(it);
|
||||
}
|
||||
return {{dependencies.begin(), dependencies.end()}, remove};
|
||||
}
|
||||
return {{}, false};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the size of the global dependency group registry.
|
||||
*
|
||||
* @return size_t - Returns the size of the global dependency groups registry.
|
||||
*/
|
||||
size_t DependencyGroup::GetRegistrySize()
|
||||
{
|
||||
std::lock_guard lock(m_RegistryMutex);
|
||||
return m_Registry.size();
|
||||
}
|
||||
|
||||
DependencyGroup::DependencyGroup(String name, const std::set<Dependency::Ptr>& dependencies)
|
||||
: m_RedundancyGroupName(std::move(name))
|
||||
{
|
||||
for (const auto& dependency : dependencies) {
|
||||
m_Members[MakeCompositeKeyFor(dependency)].emplace(dependency->GetChild().get(), dependency.get());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a composite key for the provided dependency.
|
||||
*
|
||||
* The composite key consists of all the properties of the provided dependency object that influence its availability.
|
||||
*
|
||||
* @param dependency The dependency object to create a composite key for.
|
||||
*
|
||||
* @return - Returns the composite key for the provided dependency.
|
||||
*/
|
||||
DependencyGroup::CompositeKeyType DependencyGroup::MakeCompositeKeyFor(const Dependency::Ptr& dependency)
|
||||
{
|
||||
return std::make_tuple(
|
||||
dependency->GetParent().get(),
|
||||
dependency->GetPeriod().get(),
|
||||
dependency->GetStateFilter(),
|
||||
dependency->GetIgnoreSoftStates()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current dependency has any children.
|
||||
*
|
||||
* @return bool - Returns true if the current dependency group has children, otherwise false.
|
||||
*/
|
||||
bool DependencyGroup::HasChildren() const
|
||||
{
|
||||
std::lock_guard lock(m_Mutex);
|
||||
return std::any_of(m_Members.begin(), m_Members.end(), [](const auto& pair) { return !pair.second.empty(); });
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all dependency objects of the current dependency group the provided child Checkable depend on.
|
||||
*
|
||||
* @param child The child Checkable to get the dependencies for.
|
||||
*
|
||||
* @return - Returns all the dependencies of the provided child Checkable in the current dependency group.
|
||||
*/
|
||||
std::vector<Dependency::Ptr> DependencyGroup::GetDependenciesForChild(const Checkable* child) const
|
||||
{
|
||||
std::lock_guard lock(m_Mutex);
|
||||
std::vector<Dependency::Ptr> dependencies;
|
||||
for (auto& [_, children] : m_Members) {
|
||||
auto [begin, end] = children.equal_range(child);
|
||||
std::transform(begin, end, std::back_inserter(dependencies), [](const auto& pair) {
|
||||
return pair.second;
|
||||
});
|
||||
}
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load all parent Checkables of the current dependency group.
|
||||
*
|
||||
* @param parents The set to load the parent Checkables into.
|
||||
*/
|
||||
void DependencyGroup::LoadParents(std::set<Checkable::Ptr>& parents) const
|
||||
{
|
||||
for (auto& [compositeKey, children] : m_Members) {
|
||||
parents.insert(std::get<0>(compositeKey));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the number of dependency objects in the current dependency group.
|
||||
*
|
||||
* This function mainly exists for optimization purposes, i.e. instead of getting a copy of the members and
|
||||
* counting them, we can directly query the number of dependencies in the group.
|
||||
*
|
||||
* @return size_t
|
||||
*/
|
||||
size_t DependencyGroup::GetDependenciesCount() const
|
||||
{
|
||||
std::lock_guard lock(m_Mutex);
|
||||
size_t count(0);
|
||||
for (auto& [_, dependencies] : m_Members) {
|
||||
count += dependencies.size();
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a dependency object to the current dependency group.
|
||||
*
|
||||
* @param dependency The dependency to add to the dependency group.
|
||||
*/
|
||||
void DependencyGroup::AddDependency(const Dependency::Ptr& dependency)
|
||||
{
|
||||
std::lock_guard lock(m_Mutex);
|
||||
auto compositeKey(MakeCompositeKeyFor(dependency));
|
||||
auto it = m_Members.find(compositeKey);
|
||||
|
||||
// The dependency must be compatible with the group, i.e. its parent config must be known in the group already.
|
||||
VERIFY(it != m_Members.end());
|
||||
|
||||
it->second.emplace(dependency->GetChild().get(), dependency.get());
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a dependency object from the current dependency group.
|
||||
*
|
||||
* @param dependency The dependency to remove from the dependency group.
|
||||
*/
|
||||
void DependencyGroup::RemoveDependency(const Dependency::Ptr& dependency)
|
||||
{
|
||||
std::lock_guard lock(m_Mutex);
|
||||
if (auto it(m_Members.find(MakeCompositeKeyFor(dependency))); it != m_Members.end()) {
|
||||
auto [begin, end] = it->second.equal_range(dependency->GetChild().get());
|
||||
for (auto childrenIt(begin); childrenIt != end; ++childrenIt) {
|
||||
if (childrenIt->second == dependency) {
|
||||
// This will also remove the child Checkable from the multimap container
|
||||
// entirely if this was the last child of it.
|
||||
it->second.erase(childrenIt);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy the dependency objects of the current dependency group to the provided dependency group (destination).
|
||||
*
|
||||
* @param dest The dependency group to copy the dependencies to.
|
||||
*/
|
||||
void DependencyGroup::CopyDependenciesTo(const DependencyGroup::Ptr& dest)
|
||||
{
|
||||
VERIFY(this != dest); // Prevent from doing something stupid, i.e. deadlocking ourselves.
|
||||
|
||||
std::lock_guard lock(m_Mutex);
|
||||
for (auto& [_, children] : m_Members) {
|
||||
std::for_each(children.begin(), children.end(), [&dest](const auto& pair) {
|
||||
dest->AddDependency(pair.second);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Icinga DB identifier for the current dependency group.
|
||||
*
|
||||
* The only usage of this function is the Icinga DB feature used to cache the unique hash of this dependency groups.
|
||||
*
|
||||
* @param identifier The Icinga DB identifier to set.
|
||||
*/
|
||||
void DependencyGroup::SetIcingaDBIdentifier(const String& identifier)
|
||||
{
|
||||
std::lock_guard lock(m_Mutex);
|
||||
m_IcingaDBIdentifier = identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the Icinga DB identifier for the current dependency group.
|
||||
*
|
||||
* When the identifier is not already set by Icinga DB via the SetIcingaDBIdentifier method,
|
||||
* this will just return an empty string.
|
||||
*
|
||||
* @return - Returns the Icinga DB identifier for the current dependency group.
|
||||
*/
|
||||
String DependencyGroup::GetIcingaDBIdentifier() const
|
||||
{
|
||||
std::lock_guard lock(m_Mutex);
|
||||
return m_IcingaDBIdentifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the redundancy group name of the current dependency group.
|
||||
*
|
||||
* If the current dependency group doesn't represent a redundancy group, this will return an empty string.
|
||||
*
|
||||
* @return - Returns the name of the current dependency group.
|
||||
*/
|
||||
const String& DependencyGroup::GetRedundancyGroupName() const
|
||||
{
|
||||
// We don't need to lock the mutex here, as the name is set once during
|
||||
// the object construction and never changed afterwards.
|
||||
return m_RedundancyGroupName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the unique composite key of the current dependency group.
|
||||
*
|
||||
* The composite key consists of some unique data of the group members, and should be used to generate
|
||||
* a unique deterministic hash for the dependency group. Additionally, for explicitly configured redundancy
|
||||
* groups, the non-unique dependency group name is also included on top of the composite keys.
|
||||
*
|
||||
* @return - Returns the composite key of the current dependency group.
|
||||
*/
|
||||
String DependencyGroup::GetCompositeKey()
|
||||
{
|
||||
// This a copy of the CompositeKeyType definition but with the String type instead of Checkable* and TimePeriod*.
|
||||
// This is because we need to produce a deterministic value from the composite key after each restart and that's
|
||||
// not achievable using pointers.
|
||||
using StringTuple = std::tuple<String, String, int, bool>;
|
||||
std::vector<StringTuple> compositeKeys;
|
||||
for (auto& [compositeKey, _] : m_Members) {
|
||||
auto [parent, tp, stateFilter, ignoreSoftStates] = compositeKey;
|
||||
compositeKeys.emplace_back(parent->GetName(), tp ? tp->GetName() : "", stateFilter, ignoreSoftStates);
|
||||
}
|
||||
|
||||
// IMPORTANT: The order of the composite keys must be sorted to ensure the deterministic hash value.
|
||||
std::sort(compositeKeys.begin(), compositeKeys.end());
|
||||
|
||||
Array::Ptr data(new Array{GetRedundancyGroupName()});
|
||||
for (auto& compositeKey : compositeKeys) {
|
||||
// std::apply is used to unpack the composite key tuple and add its elements to the data array.
|
||||
// It's like manually expanding the tuple into x variables and then adding them one by one to the array.
|
||||
// See https://en.cppreference.com/w/cpp/language/fold for more information.
|
||||
std::apply([&data](auto&&... args) { (data->Add(std::move(args)), ...); }, std::move(compositeKey));
|
||||
}
|
||||
|
||||
return PackObject(data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the state of the current dependency group.
|
||||
*
|
||||
* The state of the dependency group is determined based on the state of the parent Checkables and dependency objects
|
||||
* of the group. A dependency group is considered unreachable when none of the parent Checkables is reachable. However,
|
||||
* a dependency group may still be marked as failed even when it has reachable parent Checkables, but an unreachable
|
||||
* group has always a failed state.
|
||||
*
|
||||
* @param child The child Checkable to evaluate the state for.
|
||||
* @param dt The dependency type to evaluate the state for, defaults to DependencyState.
|
||||
* @param rstack The recursion stack level to prevent infinite recursion, defaults to 0.
|
||||
*
|
||||
* @return - Returns the state of the current dependency group.
|
||||
*/
|
||||
DependencyGroup::State DependencyGroup::GetState(const Checkable* child, DependencyType dt, int rstack) const
|
||||
{
|
||||
auto dependencies(GetDependenciesForChild(child));
|
||||
size_t reachable = 0, available = 0;
|
||||
|
||||
for (const auto& dependency : dependencies) {
|
||||
if (dependency->GetParent()->IsReachable(dt, rstack)) {
|
||||
reachable++;
|
||||
|
||||
// Only reachable parents are considered for availability. If they are unreachable and checks are
|
||||
// disabled, they could be incorrectly treated as available otherwise.
|
||||
if (dependency->IsAvailable(dt)) {
|
||||
available++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (IsRedundancyGroup()) {
|
||||
// The state of a redundancy group is determined by the best state of any parent. If any parent ist reachable,
|
||||
// the redundancy group is reachable, analogously for availability.
|
||||
if (reachable == 0) {
|
||||
return State::Unreachable;
|
||||
} else if (available == 0) {
|
||||
return State::Failed;
|
||||
} else {
|
||||
return State::Ok;
|
||||
}
|
||||
} else {
|
||||
// For dependencies without a redundancy group, dependencies.size() will be 1 in almost all cases. It will only
|
||||
// contain more elements if there are duplicate dependency config objects between two checkables. In this case,
|
||||
// all of them have to be reachable/available as they don't provide redundancy.
|
||||
if (reachable < dependencies.size()) {
|
||||
return State::Unreachable;
|
||||
} else if (available < dependencies.size()) {
|
||||
return State::Failed;
|
||||
} else {
|
||||
return State::Ok;
|
||||
}
|
||||
}
|
||||
}
|
@ -123,7 +123,7 @@ public:
|
||||
}
|
||||
|
||||
// Explicitly configured dependency objects
|
||||
for (const auto& dep : checkable->GetDependencies()) {
|
||||
for (const auto& dep : checkable->GetDependencies(/* includePending = */ true)) {
|
||||
m_Stack.emplace_back(dep);
|
||||
AssertNoCycle(dep->GetParent());
|
||||
m_Stack.pop_back();
|
||||
@ -251,16 +251,24 @@ void Dependency::OnAllConfigLoaded()
|
||||
// InitChildParentReferences() has to be called before.
|
||||
VERIFY(m_Child && m_Parent);
|
||||
|
||||
m_Child->AddDependency(this);
|
||||
// Icinga DB will implicitly send config updates for the parent Checkable to refresh its affects_children and
|
||||
// affected_children columns when registering the dependency from the child Checkable. So, we need to register
|
||||
// the dependency from the parent Checkable first, otherwise the config update of the parent Checkable will change
|
||||
// nothing at all.
|
||||
m_Parent->AddReverseDependency(this);
|
||||
m_Child->AddDependency(this);
|
||||
}
|
||||
|
||||
void Dependency::Stop(bool runtimeRemoved)
|
||||
{
|
||||
ObjectImpl<Dependency>::Stop(runtimeRemoved);
|
||||
|
||||
GetChild()->RemoveDependency(this);
|
||||
// Icinga DB will implicitly send config updates for the parent Checkable to refresh its affects_children and
|
||||
// affected_children columns when removing the dependency from the child Checkable. So, we need to remove the
|
||||
// dependency from the parent Checkable first, otherwise the config update of the parent Checkable will change
|
||||
// nothing at all.
|
||||
GetParent()->RemoveReverseDependency(this);
|
||||
GetChild()->RemoveDependency(this, runtimeRemoved);
|
||||
}
|
||||
|
||||
bool Dependency::IsAvailable(DependencyType dt) const
|
||||
|
@ -3,9 +3,14 @@
|
||||
#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 "config/configitem.hpp"
|
||||
#include "icinga/timeperiod.hpp"
|
||||
#include <map>
|
||||
#include <tuple>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace icinga
|
||||
{
|
||||
@ -60,6 +65,163 @@ private:
|
||||
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 */
|
||||
|
@ -20,6 +20,8 @@ public:
|
||||
|
||||
class Dependency : CustomVarObject < DependencyNameComposer
|
||||
{
|
||||
activation_priority -10;
|
||||
|
||||
load_after Host;
|
||||
load_after Service;
|
||||
|
||||
@ -77,18 +79,18 @@ class Dependency : CustomVarObject < DependencyNameComposer
|
||||
}}}
|
||||
};
|
||||
|
||||
[config] String redundancy_group;
|
||||
[config, no_user_modify] String redundancy_group;
|
||||
|
||||
[config, navigation] name(TimePeriod) period (PeriodRaw) {
|
||||
[config, no_user_modify, navigation] name(TimePeriod) period (PeriodRaw) {
|
||||
navigate {{{
|
||||
return TimePeriod::GetByName(GetPeriodRaw());
|
||||
}}}
|
||||
};
|
||||
|
||||
[config] array(Value) states;
|
||||
[config, no_user_modify] array(Value) states;
|
||||
[no_user_view, no_user_modify] int state_filter_real (StateFilter);
|
||||
|
||||
[config] bool ignore_soft_states {
|
||||
[config, no_user_modify] bool ignore_soft_states {
|
||||
default {{{ return true; }}}
|
||||
};
|
||||
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "icinga/command.hpp"
|
||||
#include "icinga/compatutility.hpp"
|
||||
#include "icinga/customvarobject.hpp"
|
||||
#include "icinga/dependency.hpp"
|
||||
#include "icinga/host.hpp"
|
||||
#include "icinga/service.hpp"
|
||||
#include "icinga/hostgroup.hpp"
|
||||
@ -94,8 +95,11 @@ void IcingaDB::ConfigStaticInitialize()
|
||||
AcknowledgementClearedHandler(checkable, removedBy, changeTime);
|
||||
});
|
||||
|
||||
Checkable::OnReachabilityChanged.connect([](const Checkable::Ptr&, const CheckResult::Ptr&, std::set<Checkable::Ptr> children, const MessageOrigin::Ptr&) {
|
||||
IcingaDB::ReachabilityChangeHandler(children);
|
||||
Checkable::OnReachabilityChanged.connect([](const Checkable::Ptr& parent, const CheckResult::Ptr&, std::set<Checkable::Ptr>, const MessageOrigin::Ptr&) {
|
||||
// Icinga DB Web needs to know about the reachability of all children, not just the direct ones.
|
||||
// These might get updated with their next check result anyway, but we can't rely on that, since
|
||||
// they might not be actively checked or have a very high check interval.
|
||||
IcingaDB::ReachabilityChangeHandler(parent->GetAllChildren());
|
||||
});
|
||||
|
||||
/* triggered on create, update and delete objects */
|
||||
@ -106,6 +110,9 @@ void IcingaDB::ConfigStaticInitialize()
|
||||
IcingaDB::VersionChangedHandler(object);
|
||||
});
|
||||
|
||||
DependencyGroup::OnChildRegistered.connect(&IcingaDB::DependencyGroupChildRegisteredHandler);
|
||||
DependencyGroup::OnChildRemoved.connect(&IcingaDB::DependencyGroupChildRemovedHandler);
|
||||
|
||||
/* downtime start */
|
||||
Downtime::OnDowntimeTriggered.connect(&IcingaDB::DowntimeStartedHandler);
|
||||
/* fixed/flexible downtime end or remove */
|
||||
@ -174,7 +181,7 @@ void IcingaDB::ConfigStaticInitialize()
|
||||
void IcingaDB::UpdateAllConfigObjects()
|
||||
{
|
||||
m_Rcon->Sync();
|
||||
m_Rcon->FireAndForgetQuery({"XADD", "icinga:schema", "MAXLEN", "1", "*", "version", "5"}, Prio::Heartbeat);
|
||||
m_Rcon->FireAndForgetQuery({"XADD", "icinga:schema", "MAXLEN", "1", "*", "version", "6"}, Prio::Heartbeat);
|
||||
|
||||
Log(LogInformation, "IcingaDB") << "Starting initial config/status dump";
|
||||
double startTime = Utility::GetTime();
|
||||
@ -203,10 +210,19 @@ void IcingaDB::UpdateAllConfigObjects()
|
||||
m_Rcon->FireAndForgetQuery({"XADD", "icinga:dump", "MAXLEN", "1", "*", "key", "*", "state", "wip"}, Prio::Config);
|
||||
|
||||
const std::vector<String> globalKeys = {
|
||||
m_PrefixConfigObject + "customvar",
|
||||
m_PrefixConfigObject + "action:url",
|
||||
m_PrefixConfigObject + "notes:url",
|
||||
m_PrefixConfigObject + "icon:image",
|
||||
m_PrefixConfigObject + "customvar",
|
||||
m_PrefixConfigObject + "action:url",
|
||||
m_PrefixConfigObject + "notes:url",
|
||||
m_PrefixConfigObject + "icon:image",
|
||||
|
||||
// These keys aren't tied to a specific Checkable type but apply to both "Host" and "Service" types,
|
||||
// and as such we've to make sure to clear them before we actually start dumping the actual objects.
|
||||
// This allows us to wait on both types to be dumped before we send a config dump done signal for those keys.
|
||||
m_PrefixConfigObject + "dependency:node",
|
||||
m_PrefixConfigObject + "dependency:edge",
|
||||
m_PrefixConfigObject + "dependency:edge:state",
|
||||
m_PrefixConfigObject + "redundancygroup",
|
||||
m_PrefixConfigObject + "redundancygroup:state",
|
||||
};
|
||||
DeleteKeys(m_Rcon, globalKeys, Prio::Config);
|
||||
DeleteKeys(m_Rcon, {"icinga:nextupdate:host", "icinga:nextupdate:service"}, Prio::Config);
|
||||
@ -217,6 +233,7 @@ void IcingaDB::UpdateAllConfigObjects()
|
||||
m_DumpedGlobals.ActionUrl.Reset();
|
||||
m_DumpedGlobals.NotesUrl.Reset();
|
||||
m_DumpedGlobals.IconImage.Reset();
|
||||
m_DumpedGlobals.DependencyGroup.Reset();
|
||||
});
|
||||
|
||||
upq.ParallelFor(types, false, [this](const Type::Ptr& type) {
|
||||
@ -259,11 +276,6 @@ void IcingaDB::UpdateAllConfigObjects()
|
||||
|
||||
upqObjectType.ParallelFor(objectChunks, [&](decltype(objectChunks)::const_reference chunk) {
|
||||
std::map<String, std::vector<String>> hMSets;
|
||||
// Two values are appended per object: Object ID (Hash encoded) and Object State (IcingaDB::SerializeState() -> JSON encoded)
|
||||
std::vector<String> states = {"HMSET", m_PrefixConfigObject + lcType + ":state"};
|
||||
// Two values are appended per object: Object ID (Hash encoded) and State Checksum ({ "checksum": checksum } -> JSON encoded)
|
||||
std::vector<String> statesChksms = {"HMSET", m_PrefixConfigCheckSum + lcType + ":state"};
|
||||
std::vector<std::vector<String> > transaction = {{"MULTI"}};
|
||||
std::vector<String> hostZAdds = {"ZADD", "icinga:nextupdate:host"}, serviceZAdds = {"ZADD", "icinga:nextupdate:service"};
|
||||
|
||||
auto skimObjects ([&]() {
|
||||
@ -303,9 +315,11 @@ void IcingaDB::UpdateAllConfigObjects()
|
||||
String objectKey = GetObjectIdentifier(object);
|
||||
Dictionary::Ptr state = SerializeState(dynamic_pointer_cast<Checkable>(object));
|
||||
|
||||
auto& states = hMSets[m_PrefixConfigObject + lcType + ":state"];
|
||||
states.emplace_back(objectKey);
|
||||
states.emplace_back(JsonEncode(state));
|
||||
|
||||
auto& statesChksms = hMSets[m_PrefixConfigCheckSum + lcType + ":state"];
|
||||
statesChksms.emplace_back(objectKey);
|
||||
statesChksms.emplace_back(JsonEncode(new Dictionary({{"checksum", HashValue(state)}})));
|
||||
}
|
||||
@ -314,27 +328,9 @@ void IcingaDB::UpdateAllConfigObjects()
|
||||
if (!(bulkCounter % 100)) {
|
||||
skimObjects();
|
||||
|
||||
for (auto& kv : hMSets) {
|
||||
if (!kv.second.empty()) {
|
||||
kv.second.insert(kv.second.begin(), {"HMSET", kv.first});
|
||||
transaction.emplace_back(std::move(kv.second));
|
||||
}
|
||||
}
|
||||
|
||||
if (states.size() > 2) {
|
||||
transaction.emplace_back(std::move(states));
|
||||
transaction.emplace_back(std::move(statesChksms));
|
||||
states = {"HMSET", m_PrefixConfigObject + lcType + ":state"};
|
||||
statesChksms = {"HMSET", m_PrefixConfigCheckSum + lcType + ":state"};
|
||||
}
|
||||
ExecuteRedisTransaction(rcon, hMSets, {});
|
||||
|
||||
hMSets = decltype(hMSets)();
|
||||
|
||||
if (transaction.size() > 1) {
|
||||
transaction.push_back({"EXEC"});
|
||||
rcon->FireAndForgetQueries(std::move(transaction), Prio::Config);
|
||||
transaction = {{"MULTI"}};
|
||||
}
|
||||
}
|
||||
|
||||
auto checkable (dynamic_pointer_cast<Checkable>(object));
|
||||
@ -357,22 +353,7 @@ void IcingaDB::UpdateAllConfigObjects()
|
||||
|
||||
skimObjects();
|
||||
|
||||
for (auto& kv : hMSets) {
|
||||
if (!kv.second.empty()) {
|
||||
kv.second.insert(kv.second.begin(), {"HMSET", kv.first});
|
||||
transaction.emplace_back(std::move(kv.second));
|
||||
}
|
||||
}
|
||||
|
||||
if (states.size() > 2) {
|
||||
transaction.emplace_back(std::move(states));
|
||||
transaction.emplace_back(std::move(statesChksms));
|
||||
}
|
||||
|
||||
if (transaction.size() > 1) {
|
||||
transaction.push_back({"EXEC"});
|
||||
rcon->FireAndForgetQueries(std::move(transaction), Prio::Config);
|
||||
}
|
||||
ExecuteRedisTransaction(rcon, hMSets, {});
|
||||
|
||||
for (auto zAdds : {&hostZAdds, &serviceZAdds}) {
|
||||
if (zAdds->size() > 2u) {
|
||||
@ -788,6 +769,8 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
|
||||
}
|
||||
}
|
||||
|
||||
InsertCheckableDependencies(checkable, hMSets, runtimeUpdate ? &runtimeUpdates : nullptr);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1121,6 +1104,195 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the dependency data for a Checkable object into the given Redis HMSETs and runtime updates.
|
||||
*
|
||||
* This function is responsible for serializing the in memory representation Checkable dependencies into
|
||||
* Redis HMSETs and runtime updates (if any) according to the Icinga DB schema. The serialized data consists
|
||||
* of the following Redis HMSETs:
|
||||
* - RedisKey::DependencyNode: Contains dependency node data representing each host, service, and redundancy group
|
||||
* in the dependency graph.
|
||||
* - RedisKey::DependencyEdge: Dependency edge information representing all connections between the nodes.
|
||||
* - RedisKey::RedundancyGroup: Redundancy group data representing all redundancy groups in the graph.
|
||||
* - RedisKey::RedundancyGroupState: State information for redundancy groups.
|
||||
* - RedisKey::DependencyEdgeState: State information for (each) dependency edge. Multiple edges may share the
|
||||
* same state.
|
||||
*
|
||||
* If the `onlyDependencyGroup` parameter is set, only dependencies from this group are processed. This is useful
|
||||
* when only a specific dependency group should be processed, e.g. during runtime updates. For initial config dumps,
|
||||
* it shouldn't be necessary to set the `runtimeUpdates` and `onlyDependencyGroup` parameters.
|
||||
*
|
||||
* @param checkable The checkable object to extract dependencies from.
|
||||
* @param hMSets The map of Redis HMSETs to insert the dependency data into.
|
||||
* @param runtimeUpdates If set, runtime updates are additionally added to this vector.
|
||||
* @param onlyDependencyGroup If set, only process dependency objects from this group.
|
||||
*/
|
||||
void IcingaDB::InsertCheckableDependencies(
|
||||
const Checkable::Ptr& checkable,
|
||||
std::map<String, RedisConnection::Query>& hMSets,
|
||||
std::vector<Dictionary::Ptr>* runtimeUpdates,
|
||||
const DependencyGroup::Ptr& onlyDependencyGroup
|
||||
)
|
||||
{
|
||||
// Only generate a dependency node event if the Checkable is actually part of some dependency graph.
|
||||
// That's, it either depends on other Checkables or others depend on it, and in both cases, we have
|
||||
// to at least generate a dependency node entry for it.
|
||||
if (!checkable->HasAnyDependencies()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First and foremost, generate a dependency node entry for the provided Checkable object and insert it into
|
||||
// the HMSETs map and if set, the `runtimeUpdates` vector.
|
||||
auto [host, service] = GetHostService(checkable);
|
||||
auto checkableId(GetObjectIdentifier(checkable));
|
||||
{
|
||||
Dictionary::Ptr data(new Dictionary{{"environment_id", m_EnvironmentId}, {"host_id", GetObjectIdentifier(host)}});
|
||||
if (service) {
|
||||
data->Set("service_id", checkableId);
|
||||
}
|
||||
|
||||
AddDataToHmSets(hMSets, RedisKey::DependencyNode, checkableId, data);
|
||||
if (runtimeUpdates) {
|
||||
AddObjectDataToRuntimeUpdates(*runtimeUpdates, checkableId, m_PrefixConfigObject + "dependency:node", data);
|
||||
}
|
||||
}
|
||||
|
||||
// If `onlyDependencyGroup` is provided, process the dependencies only from that group; otherwise,
|
||||
// retrieve all the dependency groups that the Checkable object is part of.
|
||||
std::vector<DependencyGroup::Ptr> dependencyGroups{onlyDependencyGroup};
|
||||
if (!onlyDependencyGroup) {
|
||||
dependencyGroups = checkable->GetDependencyGroups();
|
||||
}
|
||||
|
||||
for (auto& dependencyGroup : dependencyGroups) {
|
||||
String edgeFromNodeId(checkableId);
|
||||
bool syncSharedEdgeState(false);
|
||||
|
||||
if (!dependencyGroup->IsRedundancyGroup()) {
|
||||
// Non-redundant dependency groups are just placeholders and never get synced to Redis, thus just figure
|
||||
// out whether we have to sync the shared edge state. For runtime updates the states are sent via the
|
||||
// UpdateDependenciesState() method, thus we don't have to sync them here.
|
||||
syncSharedEdgeState = !runtimeUpdates && m_DumpedGlobals.DependencyGroup.IsNew(dependencyGroup->GetCompositeKey());
|
||||
} else {
|
||||
auto redundancyGroupId(HashValue(new Array{m_EnvironmentId, dependencyGroup->GetCompositeKey()}));
|
||||
dependencyGroup->SetIcingaDBIdentifier(redundancyGroupId);
|
||||
|
||||
edgeFromNodeId = redundancyGroupId;
|
||||
|
||||
// During the initial config sync, multiple children can depend on the same redundancy group, sync it only
|
||||
// the first time it is encountered. Though, if this is a runtime update, we have to re-serialize and sync
|
||||
// the redundancy group unconditionally, as we don't know whether it was already synced or the context that
|
||||
// triggered this update.
|
||||
if (runtimeUpdates || m_DumpedGlobals.DependencyGroup.IsNew(redundancyGroupId)) {
|
||||
Dictionary::Ptr groupData(new Dictionary{
|
||||
{"environment_id", m_EnvironmentId},
|
||||
{"display_name", dependencyGroup->GetRedundancyGroupName()},
|
||||
});
|
||||
// Set/refresh the redundancy group data in the Redis HMSETs (redundancy_group database table).
|
||||
AddDataToHmSets(hMSets, RedisKey::RedundancyGroup, redundancyGroupId, groupData);
|
||||
|
||||
Dictionary::Ptr nodeData(new Dictionary{
|
||||
{"environment_id", m_EnvironmentId},
|
||||
{"redundancy_group_id", redundancyGroupId},
|
||||
});
|
||||
// Obviously, the redundancy group is part of some dependency chain, thus we have to generate
|
||||
// dependency node entry for it as well.
|
||||
AddDataToHmSets(hMSets, RedisKey::DependencyNode, redundancyGroupId, nodeData);
|
||||
|
||||
if (runtimeUpdates) {
|
||||
// Send the same data sent to the Redis HMSETs to the runtime updates stream as well.
|
||||
AddObjectDataToRuntimeUpdates(*runtimeUpdates, redundancyGroupId, m_PrefixConfigObject + "redundancygroup", groupData);
|
||||
AddObjectDataToRuntimeUpdates(*runtimeUpdates, redundancyGroupId, m_PrefixConfigObject + "dependency:node", nodeData);
|
||||
} else {
|
||||
syncSharedEdgeState = true;
|
||||
|
||||
// Serialize and sync the redundancy group state information a) to the RedundancyGroupState and b)
|
||||
// to the DependencyEdgeState HMSETs. The latter is shared by all child Checkables of the current
|
||||
// redundancy group, and since they all depend on the redundancy group, the state of that group is
|
||||
// basically the state of the dependency edges between the children and the redundancy group.
|
||||
auto stateAttrs(SerializeRedundancyGroupState(checkable, dependencyGroup));
|
||||
AddDataToHmSets(hMSets, RedisKey::RedundancyGroupState, redundancyGroupId, stateAttrs);
|
||||
AddDataToHmSets(hMSets, RedisKey::DependencyEdgeState, redundancyGroupId, Dictionary::Ptr(new Dictionary{
|
||||
{"id", redundancyGroupId},
|
||||
{"environment_id", m_EnvironmentId},
|
||||
{"failed", stateAttrs->Get("failed")},
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary::Ptr data(new Dictionary{
|
||||
{"environment_id", m_EnvironmentId},
|
||||
{"from_node_id", checkableId},
|
||||
{"to_node_id", redundancyGroupId},
|
||||
// All redundancy group members share the same state, thus use the group ID as a reference.
|
||||
{"dependency_edge_state_id", redundancyGroupId},
|
||||
{"display_name", dependencyGroup->GetRedundancyGroupName()},
|
||||
});
|
||||
|
||||
// Generate a dependency edge entry representing the connection between the Checkable and the redundancy
|
||||
// group. This Checkable dependes on the redundancy group (is a child of it), thus the "dependency_edge_state_id"
|
||||
// is set to the redundancy group ID. Note that if this group has multiple children, they all will have the
|
||||
// same "dependency_edge_state_id" value.
|
||||
auto edgeId(HashValue(new Array{checkableId, redundancyGroupId}));
|
||||
AddDataToHmSets(hMSets, RedisKey::DependencyEdge, edgeId, data);
|
||||
|
||||
if (runtimeUpdates) {
|
||||
AddObjectDataToRuntimeUpdates(*runtimeUpdates, edgeId, m_PrefixConfigObject + "dependency:edge", data);
|
||||
}
|
||||
}
|
||||
|
||||
auto dependencies(dependencyGroup->GetDependenciesForChild(checkable.get()));
|
||||
// Sort the dependencies by their parent Checkable object to ensure that all dependency objects that share the
|
||||
// same parent Checkable are placed next to each other in the container. See the while loop below for more info!
|
||||
std::sort(dependencies.begin(), dependencies.end(), [](const Dependency::Ptr& lhs, const Dependency::Ptr& rhs) {
|
||||
return lhs->GetParent() < rhs->GetParent();
|
||||
});
|
||||
|
||||
// Traverse through each dependency objects within the current dependency group the provided Checkable depend
|
||||
// on and generate a dependency edge entry. The generated dependency edge "from_node_id" may vary depending on
|
||||
// whether the dependency group is a redundancy group or not. If it's a redundancy group, the "from_node_id"
|
||||
// will be the redundancy group ID; otherwise, it will be the current Checkable ID. However, the "to_node_id"
|
||||
// value will always be the parent Checkable ID of the dependency object.
|
||||
for (auto it(dependencies.begin()); it != dependencies.end(); /* no increment */) {
|
||||
auto dependency(*it);
|
||||
auto parent(dependency->GetParent());
|
||||
auto displayName(dependency->GetShortName());
|
||||
|
||||
Dictionary::Ptr edgeStateAttrs(SerializeDependencyEdgeState(dependencyGroup, dependency));
|
||||
|
||||
// In case there are multiple Dependency objects with the same parent, these are merged into a single edge
|
||||
// to prevent duplicate edges in the resulting graph. All objects with the same parent were placed next to
|
||||
// each other by the sort function above.
|
||||
//
|
||||
// Additionally, the iterator for the surrounding loop is incremented by this loop: after it has finished,
|
||||
// "it" will either point to the next dependency with a different parent or to the end of the container.
|
||||
while (++it != dependencies.end() && (*it)->GetParent() == parent) {
|
||||
displayName += ", " + (*it)->GetShortName();
|
||||
if (syncSharedEdgeState && edgeStateAttrs->Get("failed") == false) {
|
||||
edgeStateAttrs = SerializeDependencyEdgeState(dependencyGroup, *it);
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary::Ptr data(new Dictionary{
|
||||
{"environment_id", m_EnvironmentId},
|
||||
{"from_node_id", edgeFromNodeId},
|
||||
{"to_node_id", GetObjectIdentifier(parent)},
|
||||
{"dependency_edge_state_id", edgeStateAttrs->Get("id")},
|
||||
{"display_name", std::move(displayName)},
|
||||
});
|
||||
|
||||
auto edgeId(HashValue(new Array{data->Get("from_node_id"), data->Get("to_node_id")}));
|
||||
AddDataToHmSets(hMSets, RedisKey::DependencyEdge, edgeId, data);
|
||||
|
||||
if (runtimeUpdates) {
|
||||
AddObjectDataToRuntimeUpdates(*runtimeUpdates, edgeId, m_PrefixConfigObject + "dependency:edge", data);
|
||||
} else if (syncSharedEdgeState) {
|
||||
AddDataToHmSets(hMSets, RedisKey::DependencyEdgeState, edgeStateAttrs->Get("id"), edgeStateAttrs);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the state information of a checkable in Redis.
|
||||
*
|
||||
@ -1177,6 +1349,115 @@ void IcingaDB::UpdateState(const Checkable::Ptr& checkable, StateUpdate mode)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send dependencies state information of the given Checkable to Redis.
|
||||
*
|
||||
* If the dependencyGroup parameter is set, only the dependencies state of that group are sent. Otherwise, all
|
||||
* dependency groups of the provided Checkable are processed.
|
||||
*
|
||||
* @param checkable The Checkable you want to send the dependencies state update for
|
||||
* @param onlyDependencyGroup If set, send state updates only for this dependency group and its dependencies.
|
||||
* @param seenGroups A container to track already processed DependencyGroups to avoid duplicate state updates.
|
||||
*/
|
||||
void IcingaDB::UpdateDependenciesState(const Checkable::Ptr& checkable, const DependencyGroup::Ptr& onlyDependencyGroup,
|
||||
std::set<DependencyGroup*>* seenGroups) const
|
||||
{
|
||||
if (!m_Rcon || !m_Rcon->IsConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<DependencyGroup::Ptr> dependencyGroups{onlyDependencyGroup};
|
||||
if (!onlyDependencyGroup) {
|
||||
dependencyGroups = checkable->GetDependencyGroups();
|
||||
if (dependencyGroups.empty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
RedisConnection::Queries streamStates;
|
||||
auto addDependencyStateToStream([this, &streamStates](const String& redisKey, const Dictionary::Ptr& stateAttrs) {
|
||||
RedisConnection::Query xAdd{
|
||||
"XADD", "icinga:runtime:state", "MAXLEN", "~", "1000000", "*", "runtime_type", "upsert",
|
||||
"redis_key", redisKey
|
||||
};
|
||||
ObjectLock olock(stateAttrs);
|
||||
for (auto& [key, value] : stateAttrs) {
|
||||
xAdd.emplace_back(key);
|
||||
xAdd.emplace_back(IcingaToStreamValue(value));
|
||||
}
|
||||
streamStates.emplace_back(std::move(xAdd));
|
||||
});
|
||||
|
||||
std::map<String, RedisConnection::Query> hMSets;
|
||||
for (auto& dependencyGroup : dependencyGroups) {
|
||||
bool isRedundancyGroup(dependencyGroup->IsRedundancyGroup());
|
||||
if (isRedundancyGroup && dependencyGroup->GetIcingaDBIdentifier().IsEmpty()) {
|
||||
// Way too soon! The Icinga DB hash will be set during the initial config dump, but this state
|
||||
// update seems to occur way too early. So, we've to skip it for now and wait for the next one.
|
||||
// The m_ConfigDumpInProgress flag is probably still set to true at this point!
|
||||
continue;
|
||||
}
|
||||
|
||||
if (seenGroups && !seenGroups->insert(dependencyGroup.get()).second) {
|
||||
// Usually, if the seenGroups set is provided, IcingaDB is triggering a runtime state update for ALL
|
||||
// children of a given initiator Checkable (parent). In such cases, we may end up with lots of useless
|
||||
// state updates as all the children of a non-redundant group a) share the same entry in the database b)
|
||||
// it doesn't matter which child triggers the state update first all the subsequent updates are just useless.
|
||||
//
|
||||
// Likewise, for redundancy groups, all children of a redundancy group share the same set of parents
|
||||
// and thus the resulting state information would be the same from each child Checkable perspective.
|
||||
// So, serializing the redundancy group state information only once is sufficient.
|
||||
continue;
|
||||
}
|
||||
|
||||
auto dependencies(dependencyGroup->GetDependenciesForChild(checkable.get()));
|
||||
std::sort(dependencies.begin(), dependencies.end(), [](const Dependency::Ptr& lhs, const Dependency::Ptr& rhs) {
|
||||
return lhs->GetParent() < rhs->GetParent();
|
||||
});
|
||||
for (auto it(dependencies.begin()); it != dependencies.end(); /* no increment */) {
|
||||
const auto& dependency(*it);
|
||||
|
||||
Dictionary::Ptr stateAttrs;
|
||||
// Note: The following loop is intended to cover some possible special cases but may not occur in practice
|
||||
// that often. That is, having two or more dependency objects that point to the same parent Checkable.
|
||||
// So, traverse all those duplicates and merge their relevant state information into a single edge.
|
||||
for (; it != dependencies.end() && (*it)->GetParent() == dependency->GetParent(); ++it) {
|
||||
if (!stateAttrs || stateAttrs->Get("failed") == false) {
|
||||
stateAttrs = SerializeDependencyEdgeState(dependencyGroup, *it);
|
||||
}
|
||||
}
|
||||
|
||||
addDependencyStateToStream(m_PrefixConfigObject + "dependency:edge:state", stateAttrs);
|
||||
AddDataToHmSets(hMSets, RedisKey::DependencyEdgeState, stateAttrs->Get("id"), stateAttrs);
|
||||
}
|
||||
|
||||
if (isRedundancyGroup) {
|
||||
Dictionary::Ptr stateAttrs(SerializeRedundancyGroupState(checkable, dependencyGroup));
|
||||
|
||||
Dictionary::Ptr sharedGroupState(stateAttrs->ShallowClone());
|
||||
sharedGroupState->Remove("redundancy_group_id");
|
||||
sharedGroupState->Remove("is_reachable");
|
||||
sharedGroupState->Remove("last_state_change");
|
||||
|
||||
addDependencyStateToStream(m_PrefixConfigObject + "redundancygroup:state", stateAttrs);
|
||||
addDependencyStateToStream(m_PrefixConfigObject + "dependency:edge:state", sharedGroupState);
|
||||
AddDataToHmSets(hMSets, RedisKey::RedundancyGroupState, dependencyGroup->GetIcingaDBIdentifier(), stateAttrs);
|
||||
AddDataToHmSets(hMSets, RedisKey::DependencyEdgeState, dependencyGroup->GetIcingaDBIdentifier(), sharedGroupState);
|
||||
}
|
||||
}
|
||||
|
||||
if (!streamStates.empty()) {
|
||||
RedisConnection::Queries queries;
|
||||
for (auto& [redisKey, query] : hMSets) {
|
||||
query.insert(query.begin(), {"HSET", redisKey});
|
||||
queries.emplace_back(std::move(query));
|
||||
}
|
||||
|
||||
m_Rcon->FireAndForgetQueries(std::move(queries), Prio::RuntimeStateSync);
|
||||
m_Rcon->FireAndForgetQueries(std::move(streamStates), Prio::RuntimeStateStream, {0, 1});
|
||||
}
|
||||
}
|
||||
|
||||
// Used to update a single object, used for runtime updates
|
||||
void IcingaDB::SendConfigUpdate(const ConfigObject::Ptr& object, bool runtimeUpdate)
|
||||
{
|
||||
@ -1194,34 +1475,7 @@ void IcingaDB::SendConfigUpdate(const ConfigObject::Ptr& object, bool runtimeUpd
|
||||
UpdateState(checkable, runtimeUpdate ? StateUpdate::Full : StateUpdate::Volatile);
|
||||
}
|
||||
|
||||
std::vector<std::vector<String> > transaction = {{"MULTI"}};
|
||||
|
||||
for (auto& kv : hMSets) {
|
||||
if (!kv.second.empty()) {
|
||||
kv.second.insert(kv.second.begin(), {"HMSET", kv.first});
|
||||
transaction.emplace_back(std::move(kv.second));
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& objectAttributes : runtimeUpdates) {
|
||||
std::vector<String> xAdd({"XADD", "icinga:runtime", "MAXLEN", "~", "1000000", "*"});
|
||||
ObjectLock olock(objectAttributes);
|
||||
|
||||
for (const Dictionary::Pair& kv : objectAttributes) {
|
||||
String value = IcingaToStreamValue(kv.second);
|
||||
if (!value.IsEmpty()) {
|
||||
xAdd.emplace_back(kv.first);
|
||||
xAdd.emplace_back(value);
|
||||
}
|
||||
}
|
||||
|
||||
transaction.emplace_back(std::move(xAdd));
|
||||
}
|
||||
|
||||
if (transaction.size() > 1) {
|
||||
transaction.push_back({"EXEC"});
|
||||
m_Rcon->FireAndForgetQueries(std::move(transaction), Prio::Config, {1});
|
||||
}
|
||||
ExecuteRedisTransaction(m_Rcon, hMSets, runtimeUpdates);
|
||||
|
||||
if (checkable) {
|
||||
SendNextUpdate(checkable);
|
||||
@ -1308,6 +1562,11 @@ bool IcingaDB::PrepareObject(const ConfigObject::Ptr& object, Dictionary::Ptr& a
|
||||
attributes->Set("notes", checkable->GetNotes());
|
||||
attributes->Set("icon_image_alt", checkable->GetIconImageAlt());
|
||||
|
||||
if (size_t totalChildren (checkable->GetAllChildrenCount()); totalChildren > 0) {
|
||||
// Only set the Redis key if the Checkable has actually some child dependencies.
|
||||
attributes->Set("total_children", totalChildren);
|
||||
}
|
||||
|
||||
attributes->Set("checkcommand_id", GetObjectIdentifier(checkable->GetCheckCommand()));
|
||||
|
||||
Endpoint::Ptr commandEndpoint = checkable->GetCommandEndpoint();
|
||||
@ -2580,6 +2839,98 @@ void IcingaDB::SendCustomVarsChanged(const ConfigObject::Ptr& object, const Dict
|
||||
}
|
||||
}
|
||||
|
||||
void IcingaDB::SendDependencyGroupChildRegistered(const Checkable::Ptr& child, const DependencyGroup::Ptr& dependencyGroup)
|
||||
{
|
||||
if (!m_Rcon || !m_Rcon->IsConnected()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<Dictionary::Ptr> runtimeUpdates;
|
||||
std::map<String, RedisConnection::Query> hMSets;
|
||||
InsertCheckableDependencies(child, hMSets, &runtimeUpdates, dependencyGroup);
|
||||
ExecuteRedisTransaction(m_Rcon, hMSets, runtimeUpdates);
|
||||
|
||||
UpdateState(child, StateUpdate::Full);
|
||||
UpdateDependenciesState(child, dependencyGroup);
|
||||
|
||||
std::set<Checkable::Ptr> parents;
|
||||
dependencyGroup->LoadParents(parents);
|
||||
for (const auto& parent : parents) {
|
||||
// The total_children and affects_children columns might now have different outcome, so update the parent
|
||||
// Checkable as well. The grandparent Checkable may still have wrong numbers of total children, though it's not
|
||||
// worth traversing the whole tree way up and sending config updates for each one of them, as the next Redis
|
||||
// config dump is going to fix it anyway.
|
||||
SendConfigUpdate(parent, true);
|
||||
}
|
||||
}
|
||||
|
||||
void IcingaDB::SendDependencyGroupChildRemoved(
|
||||
const DependencyGroup::Ptr& dependencyGroup,
|
||||
const std::vector<Dependency::Ptr>& dependencies,
|
||||
bool removeGroup
|
||||
)
|
||||
{
|
||||
if (!m_Rcon || !m_Rcon->IsConnected() || dependencies.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Checkable::Ptr child;
|
||||
std::set<Checkable*> detachedParents;
|
||||
for (const auto& dependency : dependencies) {
|
||||
child = dependency->GetChild(); // All dependencies have the same child.
|
||||
const auto& parent(dependency->GetParent());
|
||||
if (auto [_, inserted] = detachedParents.insert(dependency->GetParent().get()); inserted) {
|
||||
String edgeId;
|
||||
if (dependencyGroup->IsRedundancyGroup()) {
|
||||
// If the redundancy group has no members left, it's going to be removed as well, so we need to
|
||||
// delete dependency edges from that group to the parent Checkables.
|
||||
if (removeGroup) {
|
||||
auto id(HashValue(new Array{dependencyGroup->GetIcingaDBIdentifier(), GetObjectIdentifier(parent)}));
|
||||
DeleteRelationship(id, RedisKey::DependencyEdge);
|
||||
DeleteState(id, RedisKey::DependencyEdgeState);
|
||||
}
|
||||
|
||||
// Remove the connection from the child Checkable to the redundancy group.
|
||||
edgeId = HashValue(new Array{GetObjectIdentifier(child), dependencyGroup->GetIcingaDBIdentifier()});
|
||||
} else {
|
||||
// Remove the edge between the parent and child Checkable linked through the removed dependency.
|
||||
edgeId = HashValue(new Array{GetObjectIdentifier(child), GetObjectIdentifier(parent)});
|
||||
}
|
||||
|
||||
DeleteRelationship(edgeId, RedisKey::DependencyEdge);
|
||||
|
||||
// The total_children and affects_children columns might now have different outcome, so update the parent
|
||||
// Checkable as well. The grandparent Checkable may still have wrong numbers of total children, though it's
|
||||
// not worth traversing the whole tree way up and sending config updates for each one of them, as the next
|
||||
// Redis config dump is going to fix it anyway.
|
||||
SendConfigUpdate(parent, true);
|
||||
|
||||
if (!parent->HasAnyDependencies()) {
|
||||
// If the parent Checkable isn't part of any other dependency chain anymore, drop its dependency node entry.
|
||||
DeleteRelationship(GetObjectIdentifier(parent), RedisKey::DependencyNode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (removeGroup && dependencyGroup->IsRedundancyGroup()) {
|
||||
String redundancyGroupId(dependencyGroup->GetIcingaDBIdentifier());
|
||||
DeleteRelationship(redundancyGroupId, RedisKey::DependencyNode);
|
||||
DeleteRelationship(redundancyGroupId, RedisKey::RedundancyGroup);
|
||||
|
||||
DeleteState(redundancyGroupId, RedisKey::RedundancyGroupState);
|
||||
DeleteState(redundancyGroupId, RedisKey::DependencyEdgeState);
|
||||
} else if (removeGroup) {
|
||||
// Note: The Icinga DB identifier of a non-redundant dependency group is used as the edge state ID
|
||||
// and shared by all of its dependency objects. See also SerializeDependencyEdgeState() for details.
|
||||
DeleteState(dependencyGroup->GetIcingaDBIdentifier(), RedisKey::DependencyEdgeState);
|
||||
}
|
||||
|
||||
if (!child->HasAnyDependencies()) {
|
||||
// If the child Checkable has no parent and reverse dependencies, we can safely remove the dependency node.
|
||||
DeleteRelationship(GetObjectIdentifier(child), RedisKey::DependencyNode);
|
||||
}
|
||||
}
|
||||
|
||||
Dictionary::Ptr IcingaDB::SerializeState(const Checkable::Ptr& checkable)
|
||||
{
|
||||
Dictionary::Ptr attrs = new Dictionary();
|
||||
@ -2623,6 +2974,7 @@ Dictionary::Ptr IcingaDB::SerializeState(const Checkable::Ptr& checkable)
|
||||
attrs->Set("check_attempt", checkable->GetCheckAttempt());
|
||||
|
||||
attrs->Set("is_active", checkable->IsActive());
|
||||
attrs->Set("affects_children", checkable->AffectsChildren());
|
||||
|
||||
CheckResult::Ptr cr = checkable->GetLastCheckResult();
|
||||
|
||||
@ -2758,8 +3110,10 @@ void IcingaDB::StateChangeHandler(const ConfigObject::Ptr& object, const CheckRe
|
||||
void IcingaDB::ReachabilityChangeHandler(const std::set<Checkable::Ptr>& children)
|
||||
{
|
||||
for (const IcingaDB::Ptr& rw : ConfigType::GetObjectsByType<IcingaDB>()) {
|
||||
std::set<DependencyGroup*> seenGroups;
|
||||
for (auto& checkable : children) {
|
||||
rw->UpdateState(checkable, StateUpdate::Full);
|
||||
rw->UpdateDependenciesState(checkable, nullptr, &seenGroups);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2856,6 +3210,20 @@ void IcingaDB::NextCheckUpdatedHandler(const Checkable::Ptr& checkable)
|
||||
}
|
||||
}
|
||||
|
||||
void IcingaDB::DependencyGroupChildRegisteredHandler(const Checkable::Ptr& child, const DependencyGroup::Ptr& dependencyGroup)
|
||||
{
|
||||
for (const auto& rw : ConfigType::GetObjectsByType<IcingaDB>()) {
|
||||
rw->SendDependencyGroupChildRegistered(child, dependencyGroup);
|
||||
}
|
||||
}
|
||||
|
||||
void IcingaDB::DependencyGroupChildRemovedHandler(const DependencyGroup::Ptr& dependencyGroup, const std::vector<Dependency::Ptr>& dependencies, bool removeGroup)
|
||||
{
|
||||
for (const auto& rw : ConfigType::GetObjectsByType<IcingaDB>()) {
|
||||
rw->SendDependencyGroupChildRemoved(dependencyGroup, dependencies, removeGroup);
|
||||
}
|
||||
}
|
||||
|
||||
void IcingaDB::HostProblemChangedHandler(const Service::Ptr& service) {
|
||||
for (auto& rw : ConfigType::GetObjectsByType<IcingaDB>()) {
|
||||
/* Host state changes affect is_handled and severity of services. */
|
||||
@ -2973,3 +3341,139 @@ void IcingaDB::DeleteRelationship(const String& id, const String& redisKeyWithou
|
||||
|
||||
m_Rcon->FireAndForgetQueries(queries, Prio::Config);
|
||||
}
|
||||
|
||||
void IcingaDB::DeleteRelationship(const String& id, RedisKey redisKey, bool hasChecksum)
|
||||
{
|
||||
switch (redisKey) {
|
||||
case RedisKey::RedundancyGroup:
|
||||
DeleteRelationship(id, "redundancygroup", hasChecksum);
|
||||
break;
|
||||
case RedisKey::DependencyNode:
|
||||
DeleteRelationship(id, "dependency:node", hasChecksum);
|
||||
break;
|
||||
case RedisKey::DependencyEdge:
|
||||
DeleteRelationship(id, "dependency:edge", hasChecksum);
|
||||
break;
|
||||
default:
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid RedisKey provided"));
|
||||
}
|
||||
}
|
||||
|
||||
void IcingaDB::DeleteState(const String& id, RedisKey redisKey, bool hasChecksum) const
|
||||
{
|
||||
String redisKeyWithoutPrefix;
|
||||
switch (redisKey) {
|
||||
case RedisKey::RedundancyGroupState:
|
||||
redisKeyWithoutPrefix = "redundancygroup:state";
|
||||
break;
|
||||
case RedisKey::DependencyEdgeState:
|
||||
redisKeyWithoutPrefix = "dependency:edge:state";
|
||||
break;
|
||||
default:
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid state RedisKey provided"));
|
||||
}
|
||||
|
||||
Log(LogNotice, "IcingaDB")
|
||||
<< "Deleting state " << std::quoted(redisKeyWithoutPrefix.CStr()) << " -> " << std::quoted(id.CStr());
|
||||
|
||||
RedisConnection::Queries hdels;
|
||||
if (hasChecksum) {
|
||||
hdels.emplace_back(RedisConnection::Query{"HDEL", m_PrefixConfigCheckSum + redisKeyWithoutPrefix, id});
|
||||
}
|
||||
hdels.emplace_back(RedisConnection::Query{"HDEL", m_PrefixConfigObject + redisKeyWithoutPrefix, id});
|
||||
|
||||
m_Rcon->FireAndForgetQueries(std::move(hdels), Prio::RuntimeStateSync);
|
||||
// TODO: This is currently purposefully commented out due to how Icinga DB (Go) handles runtime state
|
||||
// upsert and delete events. See https://github.com/Icinga/icingadb/pull/894 for more details.
|
||||
/*m_Rcon->FireAndForgetQueries({{
|
||||
"XADD", "icinga:runtime:state", "MAXLEN", "~", "1000000", "*",
|
||||
"redis_key", m_PrefixConfigObject + redisKeyWithoutPrefix, "id", id, "runtime_type", "delete"
|
||||
}}, Prio::RuntimeStateStream, {0, 1});*/
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the provided data to the Redis HMSETs map.
|
||||
*
|
||||
* Adds the provided data to the Redis HMSETs map for the provided Redis key. The actual Redis key is determined by
|
||||
* the provided RedisKey enum. The data will be json encoded before being added to the Redis HMSETs map.
|
||||
*
|
||||
* @param hMSets The map of RedisConnection::Query you want to add the data to.
|
||||
* @param redisKey The key of the Redis object you want to add the data to.
|
||||
* @param id Unique Redis identifier for the provided data.
|
||||
* @param data The actual data you want to add the Redis HMSETs map.
|
||||
*/
|
||||
void IcingaDB::AddDataToHmSets(std::map<String, RedisConnection::Query>& hMSets, RedisKey redisKey, const String& id, const Dictionary::Ptr& data) const
|
||||
{
|
||||
RedisConnection::Query* query;
|
||||
switch (redisKey) {
|
||||
case RedisKey::RedundancyGroup:
|
||||
query = &hMSets[m_PrefixConfigObject + "redundancygroup"];
|
||||
break;
|
||||
case RedisKey::DependencyNode:
|
||||
query = &hMSets[m_PrefixConfigObject + "dependency:node"];
|
||||
break;
|
||||
case RedisKey::DependencyEdge:
|
||||
query = &hMSets[m_PrefixConfigObject + "dependency:edge"];
|
||||
break;
|
||||
case RedisKey::RedundancyGroupState:
|
||||
query = &hMSets[m_PrefixConfigObject + "redundancygroup:state"];
|
||||
break;
|
||||
case RedisKey::DependencyEdgeState:
|
||||
query = &hMSets[m_PrefixConfigObject + "dependency:edge:state"];
|
||||
break;
|
||||
default:
|
||||
BOOST_THROW_EXCEPTION(std::invalid_argument("Invalid RedisKey provided"));
|
||||
}
|
||||
|
||||
query->emplace_back(id);
|
||||
query->emplace_back(JsonEncode(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the provided HMSET values and runtime updates in a single Redis transaction on the provided Redis connection.
|
||||
*
|
||||
* The HMSETs should just contain the necessary key value pairs to be set in Redis, i.e, without the HMSET command
|
||||
* itself. This function will then go through each of the map keys and prepend the HMSET command when transforming the
|
||||
* map into valid Redis queries. Likewise, the runtime updates should just contain the key value pairs to be streamed
|
||||
* to the icinga:runtime pipeline, and this function will generate a XADD query for each one of the vector elements.
|
||||
*
|
||||
* @param rcon The Redis connection to execute the transaction on.
|
||||
* @param hMSets A map of Redis keys and their respective HMSET values.
|
||||
* @param runtimeUpdates A list of dictionaries to be sent to the icinga:runtime stream.
|
||||
*/
|
||||
void IcingaDB::ExecuteRedisTransaction(const RedisConnection::Ptr& rcon, std::map<String, RedisConnection::Query>& hMSets,
|
||||
const std::vector<Dictionary::Ptr>& runtimeUpdates)
|
||||
{
|
||||
RedisConnection::Queries transaction{{"MULTI"}};
|
||||
for (auto& [redisKey, query] : hMSets) {
|
||||
if (!query.empty()) {
|
||||
query.insert(query.begin(), {"HSET", redisKey});
|
||||
transaction.emplace_back(std::move(query));
|
||||
}
|
||||
}
|
||||
|
||||
for (auto& attrs : runtimeUpdates) {
|
||||
RedisConnection::Query xAdd{"XADD", "icinga:runtime", "MAXLEN", "~", "1000000", "*"};
|
||||
|
||||
ObjectLock olock(attrs);
|
||||
for (auto& [key, value] : attrs) {
|
||||
if (auto streamVal(IcingaToStreamValue(value)); !streamVal.IsEmpty()) {
|
||||
xAdd.emplace_back(key);
|
||||
xAdd.emplace_back(std::move(streamVal));
|
||||
}
|
||||
}
|
||||
|
||||
transaction.emplace_back(std::move(xAdd));
|
||||
}
|
||||
|
||||
if (transaction.size() > 1) {
|
||||
transaction.emplace_back(RedisConnection::Query{"EXEC"});
|
||||
if (!runtimeUpdates.empty()) {
|
||||
rcon->FireAndForgetQueries(std::move(transaction), Prio::Config, {1});
|
||||
} else {
|
||||
// This is likely triggered by the initial Redis config dump, so a) we don't need to record the number of
|
||||
// affected objects and b) we don't really know how many objects are going to be affected by this tx.
|
||||
rcon->FireAndForgetQueries(std::move(transaction), Prio::Config);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -159,6 +159,66 @@ Dictionary::Ptr IcingaDB::SerializeVars(const Dictionary::Ptr& vars)
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize a dependency edge state for Icinga DB
|
||||
*
|
||||
* @param dependencyGroup The state of the group the dependency is part of.
|
||||
* @param dep The dependency object to serialize.
|
||||
*
|
||||
* @return A dictionary with the serialized state.
|
||||
*/
|
||||
Dictionary::Ptr IcingaDB::SerializeDependencyEdgeState(const DependencyGroup::Ptr& dependencyGroup, const Dependency::Ptr& dep)
|
||||
{
|
||||
String edgeStateId;
|
||||
// The edge state ID is computed a bit differently depending on whether this is for a redundancy group or not.
|
||||
// For redundancy groups, the state ID is supposed to represent the connection state between the redundancy group
|
||||
// and the parent Checkable of the given dependency. Hence, the outcome will always be different for each parent
|
||||
// Checkable of the redundancy group.
|
||||
if (dependencyGroup->IsRedundancyGroup()) {
|
||||
edgeStateId = HashValue(new Array{
|
||||
dependencyGroup->GetIcingaDBIdentifier(),
|
||||
GetObjectIdentifier(dep->GetParent()),
|
||||
});
|
||||
} else if (dependencyGroup->GetIcingaDBIdentifier().IsEmpty()) {
|
||||
// For non-redundant dependency groups, on the other hand, all dependency objects within that group will
|
||||
// always have the same parent Checkable. Likewise, the state ID will be always the same as well it doesn't
|
||||
// matter which dependency object is used to compute it. Therefore, it's sufficient to compute it only once
|
||||
// and all the other dependency objects can reuse the cached state ID.
|
||||
edgeStateId = HashValue(new Array{dependencyGroup->GetCompositeKey(), GetObjectIdentifier(dep->GetParent())});
|
||||
dependencyGroup->SetIcingaDBIdentifier(edgeStateId);
|
||||
} else {
|
||||
// Use the already computed state ID for the dependency group.
|
||||
edgeStateId = dependencyGroup->GetIcingaDBIdentifier();
|
||||
}
|
||||
|
||||
return new Dictionary{
|
||||
{"id", std::move(edgeStateId)},
|
||||
{"environment_id", m_EnvironmentId},
|
||||
{"failed", !dep->IsAvailable(DependencyState) || !dep->GetParent()->IsReachable()}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize the provided redundancy group state attributes.
|
||||
*
|
||||
* @param child The child checkable object to serialize the state for.
|
||||
* @param redundancyGroup The redundancy group object to serialize the state for.
|
||||
*
|
||||
* @return A dictionary with the serialized redundancy group state.
|
||||
*/
|
||||
Dictionary::Ptr IcingaDB::SerializeRedundancyGroupState(const Checkable::Ptr& child, const DependencyGroup::Ptr& redundancyGroup)
|
||||
{
|
||||
auto state(redundancyGroup->GetState(child.get()));
|
||||
return new Dictionary{
|
||||
{"id", redundancyGroup->GetIcingaDBIdentifier()},
|
||||
{"environment_id", m_EnvironmentId},
|
||||
{"redundancy_group_id", redundancyGroup->GetIcingaDBIdentifier()},
|
||||
{"failed", state != DependencyGroup::State::Ok},
|
||||
{"is_reachable", state != DependencyGroup::State::Unreachable},
|
||||
{"last_state_change", TimestampToMilliseconds(Utility::GetTime())},
|
||||
};
|
||||
}
|
||||
|
||||
const char* IcingaDB::GetNotificationTypeByEnum(NotificationType type)
|
||||
{
|
||||
switch (type) {
|
||||
|
@ -90,6 +90,15 @@ private:
|
||||
Full = Volatile | RuntimeOnly,
|
||||
};
|
||||
|
||||
enum class RedisKey : uint8_t
|
||||
{
|
||||
RedundancyGroup,
|
||||
DependencyNode,
|
||||
DependencyEdge,
|
||||
RedundancyGroupState,
|
||||
DependencyEdgeState,
|
||||
};
|
||||
|
||||
void OnConnectedHandler();
|
||||
|
||||
void PublishStatsTimerHandler();
|
||||
@ -101,8 +110,12 @@ private:
|
||||
void DeleteKeys(const RedisConnection::Ptr& conn, const std::vector<String>& keys, RedisConnection::QueryPriority priority);
|
||||
std::vector<String> GetTypeOverwriteKeys(const String& type);
|
||||
std::vector<String> GetTypeDumpSignalKeys(const Type::Ptr& type);
|
||||
void InsertCheckableDependencies(const Checkable::Ptr& checkable, std::map<String, RedisConnection::Query>& hMSets,
|
||||
std::vector<Dictionary::Ptr>* runtimeUpdates, const DependencyGroup::Ptr& onlyDependencyGroup = nullptr);
|
||||
void InsertObjectDependencies(const ConfigObject::Ptr& object, const String typeName, std::map<String, std::vector<String>>& hMSets,
|
||||
std::vector<Dictionary::Ptr>& runtimeUpdates, bool runtimeUpdate);
|
||||
void UpdateDependenciesState(const Checkable::Ptr& checkable, const DependencyGroup::Ptr& onlyDependencyGroup = nullptr,
|
||||
std::set<DependencyGroup*>* seenGroups = nullptr) const;
|
||||
void UpdateState(const Checkable::Ptr& checkable, StateUpdate mode);
|
||||
void SendConfigUpdate(const ConfigObject::Ptr& object, bool runtimeUpdate);
|
||||
void CreateConfigUpdate(const ConfigObject::Ptr& object, const String type, std::map<String, std::vector<String>>& hMSets,
|
||||
@ -112,6 +125,9 @@ private:
|
||||
void AddObjectDataToRuntimeUpdates(std::vector<Dictionary::Ptr>& runtimeUpdates, const String& objectKey,
|
||||
const String& redisKey, const Dictionary::Ptr& data);
|
||||
void DeleteRelationship(const String& id, const String& redisKeyWithoutPrefix, bool hasChecksum = false);
|
||||
void DeleteRelationship(const String& id, RedisKey redisKey, bool hasChecksum = false);
|
||||
void DeleteState(const String& id, RedisKey redisKey, bool hasChecksum = false) const;
|
||||
void AddDataToHmSets(std::map<String, RedisConnection::Query>& hMSets, RedisKey redisKey, const String& id, const Dictionary::Ptr& data) const;
|
||||
|
||||
void SendSentNotification(
|
||||
const Notification::Ptr& notification, const Checkable::Ptr& checkable, const std::set<User::Ptr>& users,
|
||||
@ -136,6 +152,8 @@ private:
|
||||
void SendCommandEnvChanged(const ConfigObject::Ptr& command, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues);
|
||||
void SendCommandArgumentsChanged(const ConfigObject::Ptr& command, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues);
|
||||
void SendCustomVarsChanged(const ConfigObject::Ptr& object, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues);
|
||||
void SendDependencyGroupChildRegistered(const Checkable::Ptr& child, const DependencyGroup::Ptr& dependencyGroup);
|
||||
void SendDependencyGroupChildRemoved(const DependencyGroup::Ptr& dependencyGroup, const std::vector<Dependency::Ptr>& dependencies, bool removeGroup);
|
||||
|
||||
void ForwardHistoryEntries();
|
||||
|
||||
@ -157,6 +175,8 @@ private:
|
||||
static String CalcEventID(const char* eventType, const ConfigObject::Ptr& object, double eventTime = 0, NotificationType nt = NotificationType(0));
|
||||
static const char* GetNotificationTypeByEnum(NotificationType type);
|
||||
static Dictionary::Ptr SerializeVars(const Dictionary::Ptr& vars);
|
||||
static Dictionary::Ptr SerializeDependencyEdgeState(const DependencyGroup::Ptr& dependencyGroup, const Dependency::Ptr& dep);
|
||||
static Dictionary::Ptr SerializeRedundancyGroupState(const Checkable::Ptr& child, const DependencyGroup::Ptr& redundancyGroup);
|
||||
|
||||
static String HashValue(const Value& value);
|
||||
static String HashValue(const Value& value, const std::set<String>& propertiesBlacklist, bool propertiesWhitelist = false);
|
||||
@ -180,6 +200,8 @@ private:
|
||||
static void FlappingChangeHandler(const Checkable::Ptr& checkable, double changeTime);
|
||||
static void NewCheckResultHandler(const Checkable::Ptr& checkable);
|
||||
static void NextCheckUpdatedHandler(const Checkable::Ptr& checkable);
|
||||
static void DependencyGroupChildRegisteredHandler(const Checkable::Ptr& child, const DependencyGroup::Ptr& dependencyGroup);
|
||||
static void DependencyGroupChildRemovedHandler(const DependencyGroup::Ptr& dependencyGroup, const std::vector<Dependency::Ptr>& dependencies, bool removeGroup);
|
||||
static void HostProblemChangedHandler(const Service::Ptr& service);
|
||||
static void AcknowledgementSetHandler(const Checkable::Ptr& checkable, const String& author, const String& comment, AcknowledgementType type, bool persistent, double changeTime, double expiry);
|
||||
static void AcknowledgementClearedHandler(const Checkable::Ptr& checkable, const String& removedBy, double changeTime);
|
||||
@ -195,6 +217,9 @@ private:
|
||||
static void CommandArgumentsChangedHandler(const ConfigObject::Ptr& command, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues);
|
||||
static void CustomVarsChangedHandler(const ConfigObject::Ptr& object, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues);
|
||||
|
||||
static void ExecuteRedisTransaction(const RedisConnection::Ptr& rcon, std::map<String, RedisConnection::Query>& hMSets,
|
||||
const std::vector<Dictionary::Ptr>& runtimeUpdates);
|
||||
|
||||
void AssertOnWorkQueue();
|
||||
|
||||
void ExceptionHandler(boost::exception_ptr exp);
|
||||
@ -225,7 +250,7 @@ private:
|
||||
std::atomic_size_t m_PendingRcons;
|
||||
|
||||
struct {
|
||||
DumpedGlobals CustomVar, ActionUrl, NotesUrl, IconImage;
|
||||
DumpedGlobals CustomVar, ActionUrl, NotesUrl, IconImage, DependencyGroup;
|
||||
} m_DumpedGlobals;
|
||||
|
||||
// m_EnvironmentId is shared across all IcingaDB objects (typically there is at most one, but it is perfectly fine
|
||||
|
@ -232,6 +232,10 @@ add_boost_test(base
|
||||
icinga_checkresult/service_flapping_notification
|
||||
icinga_checkresult/suppressed_notification
|
||||
icinga_dependencies/multi_parent
|
||||
icinga_dependencies/push_dependency_groups_to_registry
|
||||
icinga_dependencies/default_redundancy_group_registration_unregistration
|
||||
icinga_dependencies/simple_redundancy_group_registration_unregistration
|
||||
icinga_dependencies/mixed_redundancy_group_registration_unregsitration
|
||||
icinga_notification/strings
|
||||
icinga_notification/state_filter
|
||||
icinga_notification/type_filter
|
||||
|
@ -9,6 +9,67 @@ using namespace icinga;
|
||||
|
||||
BOOST_AUTO_TEST_SUITE(icinga_dependencies)
|
||||
|
||||
static Host::Ptr CreateHost(const std::string& name, bool pushDependencyGroupsToRegistry = true)
|
||||
{
|
||||
Host::Ptr host = new Host();
|
||||
host->SetName(name);
|
||||
if (pushDependencyGroupsToRegistry) {
|
||||
host->PushDependencyGroupsToRegistry();
|
||||
}
|
||||
return host;
|
||||
}
|
||||
|
||||
static Dependency::Ptr CreateDependency(Checkable::Ptr parent, Checkable::Ptr child, const String& name)
|
||||
{
|
||||
Dependency::Ptr dep = new Dependency();
|
||||
dep->SetParent(parent);
|
||||
dep->SetChild(child);
|
||||
dep->SetName(name + "!" + child->GetName());
|
||||
return dep;
|
||||
}
|
||||
|
||||
static void RegisterDependency(Dependency::Ptr dep, const String& redundancyGroup)
|
||||
{
|
||||
dep->SetRedundancyGroup(redundancyGroup);
|
||||
dep->GetChild()->AddDependency(dep);
|
||||
dep->GetParent()->AddReverseDependency(dep);
|
||||
}
|
||||
|
||||
static void AssertCheckableRedundancyGroup(Checkable::Ptr checkable, int dependencyCount, int groupCount, int totalDependenciesCount)
|
||||
{
|
||||
BOOST_CHECK_MESSAGE(
|
||||
dependencyCount == checkable->GetDependencies().size(),
|
||||
"Dependency count mismatch for '" << checkable->GetName() << "' - expected=" << dependencyCount << "; got="
|
||||
<< checkable->GetDependencies().size()
|
||||
);
|
||||
auto dependencyGroups(checkable->GetDependencyGroups());
|
||||
BOOST_CHECK_MESSAGE(
|
||||
groupCount == dependencyGroups.size(),
|
||||
"Dependency group count mismatch for '" << checkable->GetName() << "' - expected=" << groupCount
|
||||
<< "; got=" << dependencyGroups.size()
|
||||
);
|
||||
|
||||
for (auto& dependencyGroup : dependencyGroups) {
|
||||
BOOST_CHECK_MESSAGE(
|
||||
totalDependenciesCount == dependencyGroup->GetDependenciesCount(),
|
||||
"Dependency group '" << dependencyGroup->GetRedundancyGroupName() << "' and Checkable '" << checkable->GetName()
|
||||
<< "' total dependencies count mismatch - expected=" << totalDependenciesCount << "; got="
|
||||
<< dependencyGroup->GetDependenciesCount()
|
||||
);
|
||||
}
|
||||
|
||||
if (groupCount > 0) {
|
||||
BOOST_REQUIRE_MESSAGE(!dependencyGroups.empty(), "Checkable '" << checkable->GetName() << "' should have at least one dependency group.");
|
||||
}
|
||||
}
|
||||
|
||||
static std::vector<DependencyGroup::Ptr> ExtractGroups(const Checkable::Ptr& checkable)
|
||||
{
|
||||
auto dependencyGroups(checkable->GetDependencyGroups());
|
||||
std::sort(dependencyGroups.begin(), dependencyGroups.end());
|
||||
return dependencyGroups;
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(multi_parent)
|
||||
{
|
||||
/* One child host, two parent hosts. Simulate multi-parent dependencies. */
|
||||
@ -20,53 +81,28 @@ BOOST_AUTO_TEST_CASE(multi_parent)
|
||||
* - Parent objects need a CheckResult object
|
||||
* - Dependencies need a StateFilter
|
||||
*/
|
||||
Host::Ptr parentHost1 = new Host();
|
||||
parentHost1->SetActive(true);
|
||||
parentHost1->SetMaxCheckAttempts(1);
|
||||
parentHost1->Activate();
|
||||
parentHost1->SetAuthority(true);
|
||||
Host::Ptr parentHost1 = CreateHost("parentHost1");
|
||||
parentHost1->SetStateRaw(ServiceCritical);
|
||||
parentHost1->SetStateType(StateTypeHard);
|
||||
parentHost1->SetLastCheckResult(new CheckResult());
|
||||
|
||||
Host::Ptr parentHost2 = new Host();
|
||||
parentHost2->SetActive(true);
|
||||
parentHost2->SetMaxCheckAttempts(1);
|
||||
parentHost2->Activate();
|
||||
parentHost2->SetAuthority(true);
|
||||
Host::Ptr parentHost2 = CreateHost("parentHost2");
|
||||
parentHost2->SetStateRaw(ServiceOK);
|
||||
parentHost2->SetStateType(StateTypeHard);
|
||||
parentHost2->SetLastCheckResult(new CheckResult());
|
||||
|
||||
Host::Ptr childHost = new Host();
|
||||
childHost->SetActive(true);
|
||||
childHost->SetMaxCheckAttempts(1);
|
||||
childHost->Activate();
|
||||
childHost->SetAuthority(true);
|
||||
Host::Ptr childHost = CreateHost("childHost");
|
||||
childHost->SetStateRaw(ServiceOK);
|
||||
childHost->SetStateType(StateTypeHard);
|
||||
|
||||
/* Build the dependency tree. */
|
||||
Dependency::Ptr dep1 = new Dependency();
|
||||
|
||||
dep1->SetParent(parentHost1);
|
||||
dep1->SetChild(childHost);
|
||||
Dependency::Ptr dep1 (CreateDependency(parentHost1, childHost, "dep1"));
|
||||
dep1->SetStateFilter(StateFilterUp);
|
||||
RegisterDependency(dep1, "");
|
||||
|
||||
// Reverse dependencies
|
||||
childHost->AddDependency(dep1);
|
||||
parentHost1->AddReverseDependency(dep1);
|
||||
|
||||
Dependency::Ptr dep2 = new Dependency();
|
||||
|
||||
dep2->SetParent(parentHost2);
|
||||
dep2->SetChild(childHost);
|
||||
Dependency::Ptr dep2 (CreateDependency(parentHost2, childHost, "dep2"));
|
||||
dep2->SetStateFilter(StateFilterUp);
|
||||
|
||||
// Reverse dependencies
|
||||
childHost->AddDependency(dep2);
|
||||
parentHost2->AddReverseDependency(dep2);
|
||||
|
||||
RegisterDependency(dep2, "");
|
||||
|
||||
/* Test the reachability from this point.
|
||||
* parentHost1 is DOWN, parentHost2 is UP.
|
||||
@ -77,18 +113,42 @@ BOOST_AUTO_TEST_CASE(multi_parent)
|
||||
|
||||
BOOST_CHECK(childHost->IsReachable() == false);
|
||||
|
||||
Dependency::Ptr duplicateDep (CreateDependency(parentHost1, childHost, "dep4"));
|
||||
duplicateDep->SetIgnoreSoftStates(false, true);
|
||||
RegisterDependency(duplicateDep, "");
|
||||
parentHost1->SetStateType(StateTypeSoft);
|
||||
|
||||
// It should still be unreachable, due to the duplicated dependency object above with ignore_soft_states set to false.
|
||||
BOOST_CHECK(childHost->IsReachable() == false);
|
||||
parentHost1->SetStateType(StateTypeHard);
|
||||
childHost->RemoveDependency(duplicateDep);
|
||||
|
||||
/* The only DNS server is DOWN.
|
||||
* Expected result: childHost is unreachable.
|
||||
*/
|
||||
dep1->SetRedundancyGroup("DNS");
|
||||
childHost->RemoveDependency(dep1); // Remove the dep and re-add it with a configured redundancy group.
|
||||
RegisterDependency(dep1, "DNS");
|
||||
BOOST_CHECK(childHost->IsReachable() == false);
|
||||
|
||||
/* 1/2 DNS servers is DOWN.
|
||||
* Expected result: childHost is reachable.
|
||||
*/
|
||||
dep2->SetRedundancyGroup("DNS");
|
||||
childHost->RemoveDependency(dep2);
|
||||
RegisterDependency(dep2, "DNS");
|
||||
BOOST_CHECK(childHost->IsReachable() == true);
|
||||
|
||||
auto grandParentHost(CreateHost("GrandParentHost"));
|
||||
grandParentHost->SetLastCheckResult(new CheckResult());
|
||||
grandParentHost->SetStateRaw(ServiceCritical);
|
||||
grandParentHost->SetStateType(StateTypeHard);
|
||||
|
||||
Dependency::Ptr dep3 (CreateDependency(grandParentHost, parentHost1, "dep3"));
|
||||
dep3->SetStateFilter(StateFilterUp);
|
||||
RegisterDependency(dep3, "");
|
||||
// The grandparent is DOWN but the DNS redundancy group has to be still reachable.
|
||||
BOOST_CHECK_EQUAL(true, childHost->IsReachable());
|
||||
childHost->RemoveDependency(dep3);
|
||||
|
||||
/* Both DNS servers are DOWN.
|
||||
* Expected result: childHost is unreachable.
|
||||
*/
|
||||
@ -98,4 +158,278 @@ BOOST_AUTO_TEST_CASE(multi_parent)
|
||||
BOOST_CHECK(childHost->IsReachable() == false);
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(push_dependency_groups_to_registry)
|
||||
{
|
||||
Checkable::Ptr childHostC(CreateHost("C", false));
|
||||
Checkable::Ptr childHostD(CreateHost("D", false));
|
||||
std::set<Dependency::Ptr> dependencies; // Keep track of all dependencies to avoid unexpected deletions.
|
||||
for (auto& parent : {String("A"), String("B"), String("E")}) {
|
||||
Dependency::Ptr depC(CreateDependency(CreateHost(parent), childHostC, "depC" + parent));
|
||||
Dependency::Ptr depD(CreateDependency(depC->GetParent(), childHostD, "depD" + parent));
|
||||
if (parent == "A") {
|
||||
Dependency::Ptr depCA2(CreateDependency(depC->GetParent(), childHostC, "depCA2"));
|
||||
childHostC->AddDependency(depCA2);
|
||||
dependencies.emplace(depCA2);
|
||||
} else {
|
||||
depC->SetRedundancyGroup("redundant", true);
|
||||
depD->SetRedundancyGroup("redundant", true);
|
||||
|
||||
if (parent == "B") { // Create an exact duplicate of depC, but with a different name.
|
||||
Dependency::Ptr depCB2(CreateDependency(depC->GetParent(), childHostC, "depCB2"));
|
||||
depCB2->SetRedundancyGroup("redundant", true);
|
||||
childHostC->AddDependency(depCB2);
|
||||
dependencies.emplace(depCB2);
|
||||
}
|
||||
}
|
||||
childHostC->AddDependency(depC);
|
||||
childHostD->AddDependency(depD);
|
||||
dependencies.insert({depC, depD});
|
||||
}
|
||||
|
||||
childHostC->PushDependencyGroupsToRegistry();
|
||||
childHostD->PushDependencyGroupsToRegistry();
|
||||
|
||||
BOOST_TEST(ExtractGroups(childHostC) == ExtractGroups(childHostD), boost::test_tools::per_element());
|
||||
BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize());
|
||||
for (auto& checkable : {childHostC, childHostD}) {
|
||||
BOOST_CHECK_EQUAL(2, checkable->GetDependencyGroups().size());
|
||||
for (auto& dependencyGroup : checkable->GetDependencyGroups()) {
|
||||
if (dependencyGroup->IsRedundancyGroup()) {
|
||||
BOOST_CHECK_EQUAL(5, dependencyGroup->GetDependenciesCount());
|
||||
BOOST_CHECK_EQUAL(checkable == childHostC ? 5 : 3, checkable->GetDependencies().size());
|
||||
} else {
|
||||
BOOST_CHECK_EQUAL(3, dependencyGroup->GetDependenciesCount());
|
||||
BOOST_CHECK_EQUAL(checkable == childHostC ? 5 : 3, checkable->GetDependencies().size());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(default_redundancy_group_registration_unregistration)
|
||||
{
|
||||
Checkable::Ptr childHostC(CreateHost("C"));
|
||||
Dependency::Ptr depCA(CreateDependency(CreateHost("A"), childHostC, "depCA"));
|
||||
RegisterDependency(depCA, "");
|
||||
AssertCheckableRedundancyGroup(childHostC, 1, 1, 1);
|
||||
BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize());
|
||||
|
||||
Dependency::Ptr depCB(CreateDependency(CreateHost("B"), childHostC, "depCB"));
|
||||
RegisterDependency(depCB, "");
|
||||
AssertCheckableRedundancyGroup(childHostC, 2, 2, 1);
|
||||
BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize());
|
||||
|
||||
Checkable::Ptr childHostD(CreateHost("D"));
|
||||
Dependency::Ptr depDA(CreateDependency(depCA->GetParent(), childHostD, "depDA"));
|
||||
RegisterDependency(depDA, "");
|
||||
AssertCheckableRedundancyGroup(childHostD, 1, 1, 2);
|
||||
BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize());
|
||||
|
||||
Dependency::Ptr depDB(CreateDependency(depCB->GetParent(), childHostD, "depDB"));
|
||||
RegisterDependency(depDB, "");
|
||||
AssertCheckableRedundancyGroup(childHostD, 2, 2, 2);
|
||||
AssertCheckableRedundancyGroup(childHostC, 2, 2, 2);
|
||||
BOOST_TEST(ExtractGroups(childHostC) == ExtractGroups(childHostD), boost::test_tools::per_element());
|
||||
BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize());
|
||||
|
||||
// This is an exact duplicate of depCA, but with a different dependency name.
|
||||
Dependency::Ptr depCA2(CreateDependency(depCA->GetParent(), childHostC, "depCA2"));
|
||||
// This is a duplicate of depCA, but with a different state filter.
|
||||
Dependency::Ptr depCA3(CreateDependency(depCA->GetParent(), childHostC, "depCA3"));
|
||||
depCA3->SetStateFilter(StateFilterUp, true);
|
||||
// This is a duplicate of depCA, but with a different ignore_soft_states flag.
|
||||
Dependency::Ptr depCA4(CreateDependency(depCA->GetParent(), childHostC, "depCA4"));
|
||||
depCA4->SetIgnoreSoftStates(false, true);
|
||||
|
||||
for (auto& dependency : {depCA2, depCA3, depCA4}) {
|
||||
bool isAnExactDuplicate = dependency == depCA2;
|
||||
RegisterDependency(dependency, "");
|
||||
|
||||
if (isAnExactDuplicate) {
|
||||
BOOST_TEST(ExtractGroups(childHostC) == ExtractGroups(childHostD), boost::test_tools::per_element());
|
||||
}
|
||||
|
||||
for (auto& dependencyGroup : childHostD->GetDependencyGroups()) {
|
||||
if (dependency->GetParent() == dependencyGroup->GetDependenciesForChild(childHostD.get()).front()->GetParent()) {
|
||||
BOOST_CHECK_EQUAL(isAnExactDuplicate ? 3 : 1, dependencyGroup->GetDependenciesCount());
|
||||
} else {
|
||||
BOOST_CHECK_EQUAL(2, dependencyGroup->GetDependenciesCount());
|
||||
}
|
||||
BOOST_CHECK_EQUAL(2, childHostD->GetDependencies().size());
|
||||
}
|
||||
|
||||
for (auto& dependencyGroup : childHostC->GetDependencyGroups()) {
|
||||
if (dependency->GetParent() == dependencyGroup->GetDependenciesForChild(childHostC.get()).front()->GetParent()) {
|
||||
// If depCA2 is currently being processed, then the group should have 3 dependencies, that's because
|
||||
// depCA2 is an exact duplicate of depCA, and depCA shares the same group with depDA.
|
||||
BOOST_CHECK_EQUAL(isAnExactDuplicate ? 3 : 2, dependencyGroup->GetDependenciesCount());
|
||||
} else {
|
||||
BOOST_CHECK_EQUAL(2, dependencyGroup->GetDependenciesCount());
|
||||
}
|
||||
// The 3 dependencies are depCA, depCB, and the current one from the loop.
|
||||
BOOST_CHECK_EQUAL(3, childHostC->GetDependencies().size());
|
||||
}
|
||||
BOOST_CHECK_EQUAL(isAnExactDuplicate ? 2 : 3, DependencyGroup::GetRegistrySize());
|
||||
childHostC->RemoveDependency(dependency);
|
||||
}
|
||||
|
||||
childHostC->RemoveDependency(depCA);
|
||||
childHostD->RemoveDependency(depDA);
|
||||
AssertCheckableRedundancyGroup(childHostC, 1, 1, 2);
|
||||
AssertCheckableRedundancyGroup(childHostD, 1, 1, 2);
|
||||
BOOST_TEST(ExtractGroups(childHostC) == ExtractGroups(childHostD), boost::test_tools::per_element());
|
||||
BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize());
|
||||
|
||||
childHostC->RemoveDependency(depCB);
|
||||
childHostD->RemoveDependency(depDB);
|
||||
AssertCheckableRedundancyGroup(childHostC, 0, 0, 0);
|
||||
AssertCheckableRedundancyGroup(childHostD, 0, 0, 0);
|
||||
BOOST_CHECK_EQUAL(0, DependencyGroup::GetRegistrySize());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(simple_redundancy_group_registration_unregistration)
|
||||
{
|
||||
Checkable::Ptr childHostC(CreateHost("childC"));
|
||||
|
||||
Dependency::Ptr depCA(CreateDependency(CreateHost("A"), childHostC, "depCA"));
|
||||
RegisterDependency(depCA, "redundant");
|
||||
AssertCheckableRedundancyGroup(childHostC, 1, 1, 1);
|
||||
BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize());
|
||||
|
||||
Dependency::Ptr depCB(CreateDependency(CreateHost("B"), childHostC, "depCB"));
|
||||
RegisterDependency(depCB, "redundant");
|
||||
AssertCheckableRedundancyGroup(childHostC, 2, 1, 2);
|
||||
BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize());
|
||||
|
||||
Checkable::Ptr childHostD(CreateHost("childD"));
|
||||
Dependency::Ptr depDA (CreateDependency(depCA->GetParent(), childHostD, "depDA"));
|
||||
RegisterDependency(depDA, "redundant");
|
||||
AssertCheckableRedundancyGroup(childHostD, 1, 1, 1);
|
||||
BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize());
|
||||
|
||||
Dependency::Ptr depDB(CreateDependency(depCB->GetParent(), childHostD, "depDB"));
|
||||
RegisterDependency(depDB, "redundant");
|
||||
// Still 1 redundancy group, but there should be 4 dependencies now, i.e. 2 for each child Checkable.
|
||||
AssertCheckableRedundancyGroup(childHostC, 2, 1, 4);
|
||||
AssertCheckableRedundancyGroup(childHostD, 2, 1, 4);
|
||||
BOOST_TEST(ExtractGroups(childHostC) == ExtractGroups(childHostD), boost::test_tools::per_element());
|
||||
BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize());
|
||||
|
||||
childHostC->RemoveDependency(depCA);
|
||||
// After unregistering depCA, childHostC should have a new redundancy group with only depCB as dependency, and...
|
||||
AssertCheckableRedundancyGroup(childHostC, 1, 1, 1);
|
||||
// ...childHostD should still have the same redundancy group as before but also with only two dependencies.
|
||||
AssertCheckableRedundancyGroup(childHostD, 2, 1, 2);
|
||||
BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize());
|
||||
|
||||
childHostD->RemoveDependency(depDA);
|
||||
// Nothing should have changed for childHostC, but childHostD should now have a fewer group dependency, i.e.
|
||||
// both child hosts should have the same redundancy group with only depCB and depDB as dependency.
|
||||
AssertCheckableRedundancyGroup(childHostC, 1, 1, 2);
|
||||
AssertCheckableRedundancyGroup(childHostD, 1, 1, 2);
|
||||
BOOST_TEST(ExtractGroups(childHostC) == ExtractGroups(childHostD), boost::test_tools::per_element());
|
||||
BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize());
|
||||
|
||||
RegisterDependency(depDA, depDA->GetRedundancyGroup());
|
||||
childHostD->RemoveDependency(depDB);
|
||||
// Nothing should have changed for childHostC, but both should now have a separate group with only depCB and depDA as dependency.
|
||||
AssertCheckableRedundancyGroup(childHostC, 1, 1, 1);
|
||||
AssertCheckableRedundancyGroup(childHostD, 1, 1, 1);
|
||||
BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize());
|
||||
|
||||
childHostC->RemoveDependency(depCB);
|
||||
childHostD->RemoveDependency(depDA);
|
||||
AssertCheckableRedundancyGroup(childHostC, 0, 0, 0);
|
||||
AssertCheckableRedundancyGroup(childHostD, 0, 0, 0);
|
||||
BOOST_CHECK_EQUAL(0, DependencyGroup::GetRegistrySize());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_CASE(mixed_redundancy_group_registration_unregsitration)
|
||||
{
|
||||
Checkable::Ptr childHostC(CreateHost("childC"));
|
||||
Dependency::Ptr depCA(CreateDependency(CreateHost("A"), childHostC, "depCA"));
|
||||
RegisterDependency(depCA, "redundant");
|
||||
AssertCheckableRedundancyGroup(childHostC, 1, 1, 1);
|
||||
BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize());
|
||||
|
||||
Checkable::Ptr childHostD(CreateHost("childD"));
|
||||
Dependency::Ptr depDA(CreateDependency(depCA->GetParent(), childHostD, "depDA"));
|
||||
RegisterDependency(depDA, "redundant");
|
||||
AssertCheckableRedundancyGroup(childHostD, 1, 1, 2);
|
||||
BOOST_TEST(ExtractGroups(childHostC) == ExtractGroups(childHostD), boost::test_tools::per_element());
|
||||
BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize());
|
||||
|
||||
Dependency::Ptr depCB(CreateDependency(CreateHost("B"), childHostC, "depCB"));
|
||||
RegisterDependency(depCB, "redundant");
|
||||
AssertCheckableRedundancyGroup(childHostC, 2, 1, 2);
|
||||
AssertCheckableRedundancyGroup(childHostD, 1, 1, 1);
|
||||
BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize());
|
||||
|
||||
Dependency::Ptr depDB(CreateDependency(depCB->GetParent(), childHostD, "depDB"));
|
||||
RegisterDependency(depDB, "redundant");
|
||||
AssertCheckableRedundancyGroup(childHostC, 2, 1, 4);
|
||||
AssertCheckableRedundancyGroup(childHostD, 2, 1, 4);
|
||||
BOOST_TEST(ExtractGroups(childHostC) == ExtractGroups(childHostD), boost::test_tools::per_element());
|
||||
BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize());
|
||||
|
||||
Checkable::Ptr childHostE(CreateHost("childE"));
|
||||
Dependency::Ptr depEA(CreateDependency(depCA->GetParent(), childHostE, "depEA"));
|
||||
RegisterDependency(depEA, "redundant");
|
||||
AssertCheckableRedundancyGroup(childHostE, 1, 1, 1);
|
||||
BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize());
|
||||
|
||||
Dependency::Ptr depEB(CreateDependency(depCB->GetParent(), childHostE, "depEB"));
|
||||
RegisterDependency(depEB, "redundant");
|
||||
// All 3 hosts share the same group, and each host has 2 dependencies, thus 6 dependencies in total.
|
||||
AssertCheckableRedundancyGroup(childHostC, 2, 1, 6);
|
||||
AssertCheckableRedundancyGroup(childHostD, 2, 1, 6);
|
||||
AssertCheckableRedundancyGroup(childHostE, 2, 1, 6);
|
||||
auto childHostCGroups(ExtractGroups(childHostC));
|
||||
BOOST_TEST((childHostCGroups == ExtractGroups(childHostD) && childHostCGroups == ExtractGroups(childHostE)));
|
||||
BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize());
|
||||
|
||||
Dependency::Ptr depEZ(CreateDependency(CreateHost("Z"), childHostE, "depEZ"));
|
||||
RegisterDependency(depEZ, "redundant");
|
||||
// Child host E should have a new redundancy group with 3 dependencies and the other two should still share the same group.
|
||||
AssertCheckableRedundancyGroup(childHostC, 2, 1, 4);
|
||||
AssertCheckableRedundancyGroup(childHostD, 2, 1, 4);
|
||||
BOOST_TEST(ExtractGroups(childHostC) == ExtractGroups(childHostD), boost::test_tools::per_element());
|
||||
AssertCheckableRedundancyGroup(childHostE, 3, 1, 3);
|
||||
BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize());
|
||||
|
||||
childHostE->RemoveDependency(depEA);
|
||||
AssertCheckableRedundancyGroup(childHostC, 2, 1, 4);
|
||||
AssertCheckableRedundancyGroup(childHostD, 2, 1, 4);
|
||||
BOOST_TEST(ExtractGroups(childHostC) == ExtractGroups(childHostD), boost::test_tools::per_element());
|
||||
AssertCheckableRedundancyGroup(childHostE, 2, 1, 2);
|
||||
BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize());
|
||||
|
||||
RegisterDependency(depEA, depEA->GetRedundancyGroup()); // Re-register depEA and instead...
|
||||
childHostE->RemoveDependency(depEZ); // ...unregister depEZ and check if all the hosts share the same group again.
|
||||
// All 3 hosts share the same group again, and each host has 2 dependencies, thus 6 dependencies in total.
|
||||
AssertCheckableRedundancyGroup(childHostC, 2, 1, 6);
|
||||
AssertCheckableRedundancyGroup(childHostD, 2, 1, 6);
|
||||
AssertCheckableRedundancyGroup(childHostE, 2, 1, 6);
|
||||
childHostCGroups = ExtractGroups(childHostC);
|
||||
BOOST_TEST((childHostCGroups == ExtractGroups(childHostD) && childHostCGroups == ExtractGroups(childHostE)));
|
||||
BOOST_CHECK_EQUAL(1, DependencyGroup::GetRegistrySize());
|
||||
|
||||
childHostC->RemoveDependency(depCA);
|
||||
childHostD->RemoveDependency(depDB);
|
||||
childHostE->RemoveDependency(depEB);
|
||||
// Child host C has now a separate group with only depCB as dependency, and child hosts D and E share the same group.
|
||||
AssertCheckableRedundancyGroup(childHostC, 1, 1, 1);
|
||||
AssertCheckableRedundancyGroup(childHostD, 1, 1, 2);
|
||||
AssertCheckableRedundancyGroup(childHostE, 1, 1, 2);
|
||||
BOOST_TEST(ExtractGroups(childHostD) == ExtractGroups(childHostE), boost::test_tools::per_element());
|
||||
BOOST_CHECK_EQUAL(2, DependencyGroup::GetRegistrySize());
|
||||
|
||||
childHostC->RemoveDependency(depCB);
|
||||
childHostD->RemoveDependency(depDA);
|
||||
childHostE->RemoveDependency(depEA);
|
||||
AssertCheckableRedundancyGroup(childHostC, 0, 0, 0);
|
||||
AssertCheckableRedundancyGroup(childHostD, 0, 0, 0);
|
||||
AssertCheckableRedundancyGroup(childHostE, 0, 0, 0);
|
||||
BOOST_CHECK_EQUAL(0, DependencyGroup::GetRegistrySize());
|
||||
}
|
||||
|
||||
BOOST_AUTO_TEST_SUITE_END()
|
||||
|
Loading…
x
Reference in New Issue
Block a user