From 0ab50fd82d5cdf4e9068f8d4e466f42f970d2a7f Mon Sep 17 00:00:00 2001 From: Yonas Habteab Date: Mon, 10 Feb 2025 08:40:29 +0100 Subject: [PATCH] IcingaDB: Process dependencies runtime updates --- lib/icingadb/icingadb-objects.cpp | 173 +++++++++++++++++++++++++++++- lib/icingadb/icingadb.hpp | 8 +- 2 files changed, 177 insertions(+), 4 deletions(-) diff --git a/lib/icingadb/icingadb-objects.cpp b/lib/icingadb/icingadb-objects.cpp index e407373fa..0a5e5eb0f 100644 --- a/lib/icingadb/icingadb-objects.cpp +++ b/lib/icingadb/icingadb-objects.cpp @@ -110,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 */ @@ -1115,16 +1118,20 @@ void IcingaDB::InsertObjectDependencies(const ConfigObject::Ptr& object, const S * - RedisKey::DependencyEdgeState: State information for (each) dependency edge. Multiple edges may share the * same state. * - * For initial dumps, it shouldn't be necessary to set the `runtimeUpdates` parameter. + * 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& hMSets, - std::vector* runtimeUpdates + std::vector* runtimeUpdates, + const DependencyGroup::Ptr& onlyDependencyGroup ) { // Only generate a dependency node event if the Checkable is actually part of some dependency graph. @@ -1150,7 +1157,14 @@ void IcingaDB::InsertCheckableDependencies( } } - for (auto& dependencyGroup : checkable->GetDependencyGroups()) { + // 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 dependencyGroups{onlyDependencyGroup}; + if (!onlyDependencyGroup) { + dependencyGroups = checkable->GetDependencyGroups(); + } + + for (auto& dependencyGroup : dependencyGroups) { String edgeFromNodeId(checkableId); bool syncSharedEdgeState(false); @@ -2800,6 +2814,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 runtimeUpdates; + std::map hMSets; + InsertCheckableDependencies(child, hMSets, &runtimeUpdates, dependencyGroup); + ExecuteRedisTransaction(m_Rcon, hMSets, runtimeUpdates); + + UpdateState(child, StateUpdate::Full); + UpdateDependenciesState(child, dependencyGroup); + + std::set 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& dependencies, + bool removeGroup +) +{ + if (!m_Rcon || !m_Rcon->IsConnected() || dependencies.empty()) { + return; + } + + Checkable::Ptr child; + std::set 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(); @@ -3078,6 +3184,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()) { + rw->SendDependencyGroupChildRegistered(child, dependencyGroup); + } +} + +void IcingaDB::DependencyGroupChildRemovedHandler(const DependencyGroup::Ptr& dependencyGroup, const std::vector& dependencies, bool removeGroup) +{ + for (const auto& rw : ConfigType::GetObjectsByType()) { + rw->SendDependencyGroupChildRemoved(dependencyGroup, dependencies, removeGroup); + } +} + void IcingaDB::HostProblemChangedHandler(const Service::Ptr& service) { for (auto& rw : ConfigType::GetObjectsByType()) { /* Host state changes affect is_handled and severity of services. */ @@ -3196,6 +3316,53 @@ 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); + 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. * diff --git a/lib/icingadb/icingadb.hpp b/lib/icingadb/icingadb.hpp index d8f7843a5..df454e31a 100644 --- a/lib/icingadb/icingadb.hpp +++ b/lib/icingadb/icingadb.hpp @@ -111,7 +111,7 @@ private: std::vector GetTypeOverwriteKeys(const String& type); std::vector GetTypeDumpSignalKeys(const Type::Ptr& type); void InsertCheckableDependencies(const Checkable::Ptr& checkable, std::map& hMSets, - std::vector* runtimeUpdates); + std::vector* runtimeUpdates, const DependencyGroup::Ptr& onlyDependencyGroup = nullptr); void InsertObjectDependencies(const ConfigObject::Ptr& object, const String typeName, std::map>& hMSets, std::vector& runtimeUpdates, bool runtimeUpdate); void UpdateDependenciesState(const Checkable::Ptr& checkable, const DependencyGroup::Ptr& onlyDependencyGroup = nullptr) const; @@ -124,6 +124,8 @@ private: void AddObjectDataToRuntimeUpdates(std::vector& 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& hMSets, RedisKey redisKey, const String& id, const Dictionary::Ptr& data) const; void SendSentNotification( @@ -149,6 +151,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& dependencies, bool removeGroup); void ForwardHistoryEntries(); @@ -195,6 +199,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& 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);