icinga2/lib/icingadb/icingadb.hpp
Johannes Schmidt 393724bb2a Consistently use std::exception_ptr
`DiagnosticInformation()` wasn't able to take a `std::exception_ptr` due
to the missing conversion on older boost versions, so now everything uses
the std::exception_ptr instead. There are still a few reasons to use
`boost::exception` in some places, but for exception pointers, the standard
one should be better in most cases and almost never requires to include an
extra header.
2026-07-02 13:59:48 +02:00

491 lines
22 KiB
C++

// SPDX-FileCopyrightText: 2012 Icinga GmbH <https://icinga.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#ifndef ICINGADB_H
#define ICINGADB_H
#include "icingadb/icingadb-ti.hpp"
#include "icingadb/redisconnection.hpp"
#include "base/atomic.hpp"
#include "base/bulker.hpp"
#include "base/timer.hpp"
#include "base/workqueue.hpp"
#include "icinga/customvarobject.hpp"
#include "icinga/checkable.hpp"
#include "icinga/command.hpp"
#include "icinga/service.hpp"
#include "icinga/downtime.hpp"
#include "remote/messageorigin.hpp"
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <atomic>
#include <chrono>
#include <future>
#include <memory>
#include <mutex>
#include <set>
#include <unordered_map>
#include <unordered_set>
#include <utility>
namespace icinga
{
#define CONFIG_REDIS_KEY_PREFIX "icinga:"
#define CHECKSUM_REDIS_KEY_PREFIX CONFIG_REDIS_KEY_PREFIX "checksum:"
namespace icingadb::task_queue
{
/**
* Dirty bits for config/state changes.
*
* These are used to mark objects as "dirty" in order to trigger appropriate updates in Redis.
* Each bit represents a different type of change that requires a specific action to be taken.
*
* @ingroup icingadb
*/
enum DirtyBits : uint32_t
{
ConfigUpdate = 1<<0, // Trigger a Redis config update for the object.
ConfigDelete = 1<<1, // Send a deletion command for the object to Redis.
VolatileState = 1<<2, // Send a volatile state update to Redis (affects only checkables).
RuntimeState = 1<<3, // Send a runtime state update to Redis (affects only checkables).
NextUpdate = 1<<4, // Update the `icinga:nextupdate:{host,service}` Redis keys (affects only checkables).
FullState = VolatileState | RuntimeState, // A combination of all (non-dependency) state-related dirty bits.
// All valid dirty bits combined used for masking input values.
DirtyBitsAll = ConfigUpdate | ConfigDelete | FullState | NextUpdate
};
/**
* A pending configuration object item.
*
* This struct represents a pending item in the queue that is associated with a configuration object.
* It contains a pointer to the configuration object and the dirty bits indicating the type of updates
* required for that object in Redis.
*
* @ingroup icingadb
*/
struct PendingConfigItem
{
ConfigObject::Ptr Object;
uint32_t DirtyBits;
PendingConfigItem(const ConfigObject::Ptr& obj, uint32_t bits);
[[nodiscard]] ConfigObject* GetQueueLookupKey() const
{
return Object.get();
}
};
/**
* A pending dependency group state item.
*
* This struct represents a pending item in the queue that is associated with a dependency group.
* It contains a pointer to the dependency group for which state updates are required.
*
* @ingroup icingadb
*/
struct PendingDependencyGroupStateItem
{
DependencyGroup::Ptr DepGroup;
explicit PendingDependencyGroupStateItem(const DependencyGroup::Ptr& depGroup);
[[nodiscard]] DependencyGroup* GetQueueLookupKey() const
{
return DepGroup.get();
}
};
/**
* A pending dependency edge item.
*
* This struct represents a pending dependency child registration into a dependency group.
* It contains a pointer to the dependency group and the checkable child being registered.
*
* @ingroup icingadb
*/
struct PendingDependencyEdgeItem
{
DependencyGroup::Ptr DepGroup;
Checkable::Ptr Child;
PendingDependencyEdgeItem(const DependencyGroup::Ptr& depGroup, const Checkable::Ptr& child);
[[nodiscard]] std::pair<DependencyGroup*, Checkable*> GetQueueLookupKey() const
{
return {DepGroup.get(), Child.get()};
}
};
/**
* A pending relations deletion item.
*
* This struct represents a pending item in the queue that is associated with the deletion of relations
* in Redis. It contains a map of Redis keys from which the relation identified by the @c ID field should
* be deleted. The @c ID field represents the unique identifier of the relation to be deleted, and the
* @c Relations map specifies the Redis keys and whether to delete the corresponding checksum keys.
*
* @ingroup icingadb
*/
struct RelationsDeletionItem
{
std::string ID;
// Set of Redis keys from which to delete a relation, along with their checksums (if any).
using RelationsKeySet = std::set<std::pair<RedisConnection::QueryArg, RedisConnection::QueryArg>>;
RelationsKeySet Relations;
RelationsDeletionItem(const String& id, const RelationsKeySet& relations);
[[nodiscard]] std::string_view GetQueueLookupKey() const
{
return ID;
}
};
/**
* A pending queue item.
*
* This struct represents a generic pending item in the queue. The @c EnqueueTime field records the
* time when the item was added to the queue, which can be useful for tracking how long an item waits before
* being processed. This base struct wraps one of the available more specific pending item types that operate
* on different kinds of objects, such as configuration objects or dependency groups.
*
* @ingroup icingadb
*/
struct PendingQueueItem
{
using ItemVariant = std::variant<
PendingConfigItem,
PendingDependencyGroupStateItem,
PendingDependencyEdgeItem,
RelationsDeletionItem
>;
ItemVariant Item;
std::chrono::steady_clock::time_point EnqueueTime{std::chrono::steady_clock::now()};
template<typename T,
// don't hide the default copy and move constructors
typename = std::enable_if_t<!std::is_same_v<std::decay_t<T>, PendingQueueItem>>
>
explicit PendingQueueItem(T&& item) : Item(std::forward<T>(item)) {}
};
template<typename T>
struct KeyExtractor /* not implemented, only the specialization for std::variant is used */;
/**
* Helper to use @c PendingQueueItem in @c boost::multi_index_container.
*
* It implements a key extractor that @c multi_index_container will invoke for each element to create an index.
* Every individual type in @c PendingQueueItem::ItemVariant implements a method @c GetQueueLookupKey() that will
* be invoked by this key extractor. The return value from the individual type is then returned as part of a second
* variant type (its element types are automatically deduced from the individual @c GetQueueLookupKey() methods).
* @c multi_index_container will then just use the standard comparison operators on it for building the index.
*
* @ingroup icingadb
*/
template<typename ...Ts>
struct KeyExtractor<std::variant<Ts...>> {
using result_type = std::variant<decltype(std::declval<Ts>().GetQueueLookupKey())...>;
result_type operator()(const PendingQueueItem& queueItem) const
{
return std::visit([](const auto& innerItem) -> result_type {
return innerItem.GetQueueLookupKey();
}, queueItem.Item);
};
};
// A multi-index container for managing pending items with unique IDs and maintaining insertion order.
// The first index is an ordered unique index based on the pending item key, allowing for efficient
// lookups and ensuring uniqueness of items. The second index is a sequenced index that maintains the
// order of insertion, enabling FIFO processing of pending items.
using PendingItemsSet = boost::multi_index_container<
PendingQueueItem,
boost::multi_index::indexed_by<
boost::multi_index::ordered_unique<KeyExtractor<PendingQueueItem::ItemVariant>>,
boost::multi_index::sequenced<>
>
>;
} // namespace icingadb::task_queue
/**
* @ingroup icingadb
*/
class IcingaDB : public ObjectImpl<IcingaDB>
{
public:
DECLARE_OBJECT(IcingaDB);
DECLARE_OBJECTNAME(IcingaDB);
IcingaDB();
static void ConfigStaticInitialize();
void Validate(int types, const ValidationUtils& utils) override;
virtual void Start(bool runtimeCreated) override;
virtual void Stop(bool runtimeRemoved) override;
String GetEnvironmentId() const override;
inline RedisConnection::Ptr GetConnection() const
{
return m_RconLocked.load();
}
void LoadPendingItemsStats(const Array::Ptr& perfdata) const;
template<class T>
static void AddKvsToMap(const Array::Ptr& kvs, T& map)
{
Value* key = nullptr;
ObjectLock oLock (kvs);
for (auto& kv : kvs) {
if (key) {
map.emplace(std::move(*key), std::move(kv));
key = nullptr;
} else {
key = &kv;
}
}
}
// Represents any pair of Redis keys that belong together, e.g, @c {icinga:host:state, icinga:checksum:host:state}.
struct QueryArgPair
{
std::string_view ObjectKey;
std::string_view ChecksumKey;
};
struct CmdArgEnvRedisKeys
{
std::string_view ArgObjectKey; // Represents `icinga:{command_type}:argument`.
std::string_view ArgChecksumKey; // Represents `icinga:checksum:{command_type}:argument`.
std::string_view EnvObjectKey; // Represents `icinga:{command_type}:envvar`.
std::string_view EnvChecksumKey; // Represents `icinga:checksum:{command_type}:envvar`.
};
protected:
void ValidateTlsProtocolmin(const Lazy<String>& lvalue, const ValidationUtils& utils) override;
void ValidateConnectTimeout(const Lazy<double>& lvalue, const ValidationUtils& utils) override;
RedisConnInfo::ConstPtr GetRedisConnInfo() const;
private:
class DumpedGlobals
{
public:
void Reset();
bool IsNew(const String& id);
private:
std::set<String> m_Ids;
std::mutex m_Mutex;
};
void OnConnectedHandler();
void PublishStatsTimerHandler();
void PublishStats();
/* config & status dump */
void UpdateAllConfigObjects();
std::vector<std::vector<intrusive_ptr<ConfigObject>>> ChunkObjects(std::vector<intrusive_ptr<ConfigObject>> objects, size_t chunkSize);
void InsertCheckableDependencies(const Checkable::Ptr& checkable, std::map<RedisConnection::QueryArg, RedisConnection::Query>& hMSets,
std::vector<Dictionary::Ptr>* runtimeUpdates, const DependencyGroup::Ptr& onlyDependencyGroup = nullptr);
void InsertObjectDependencies(const ConfigObject::Ptr& object,
std::map<RedisConnection::QueryArg, RedisConnection::Query>& hMSets, std::vector<Dictionary::Ptr>& runtimeUpdates, bool runtimeUpdate);
void UpdateDependenciesState(const Checkable::Ptr& checkable, const DependencyGroup::Ptr& dependencyGroup) const;
void UpdateState(const Checkable::Ptr& checkable, uint32_t mode);
void CreateConfigUpdate(const ConfigObject::Ptr& object, const QueryArgPair& redisKeyPair,
std::map<RedisConnection::QueryArg, RedisConnection::Query>& hMSets, std::vector<Dictionary::Ptr>& runtimeUpdates, bool runtimeUpdate);
void SendConfigDelete(const ConfigObject::Ptr& object);
void SendStateChange(const ConfigObject::Ptr& object, const CheckResult::Ptr& cr, StateType type);
void AddObjectDataToRuntimeUpdates(std::vector<Dictionary::Ptr>& runtimeUpdates,
String objectKey, String redisKey, const Dictionary::Ptr& data);
void DeleteRelationship(const String& id, RedisConnection::QueryArg redisObjKey, RedisConnection::QueryArg redisChecksumKey = "");
void DeleteState(const String& id, RedisConnection::QueryArg redisObjKey, RedisConnection::QueryArg redisChecksumKey = "") const;
void SendSentNotification(
const Notification::Ptr& notification, const Checkable::Ptr& checkable, const std::set<User::Ptr>& users,
NotificationType type, const CheckResult::Ptr& cr, const String& author, const String& text, double sendTime
);
void SendStartedDowntime(const Downtime::Ptr& downtime);
void SendRemovedDowntime(const Downtime::Ptr& downtime);
void SendAddedComment(const Comment::Ptr& comment);
void SendRemovedComment(const Comment::Ptr& comment);
void SendFlappingChange(const Checkable::Ptr& checkable, double changeTime, double flappingLastChange);
void SendAcknowledgementSet(const Checkable::Ptr& checkable, const String& author, const String& comment, AcknowledgementType type, bool persistent, double changeTime, double expiry);
void SendAcknowledgementCleared(const Checkable::Ptr& checkable, const String& removedBy, double changeTime, double ackLastChange);
void SendNotificationUsersChanged(const Notification::Ptr& notification, const Array::Ptr& oldValues, const Array::Ptr& newValues);
void SendNotificationUserGroupsChanged(const Notification::Ptr& notification, const Array::Ptr& oldValues, const Array::Ptr& newValues);
void SendTimePeriodRangesChanged(const TimePeriod::Ptr& timeperiod, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues);
void SendTimePeriodIncludesChanged(const TimePeriod::Ptr& timeperiod, const Array::Ptr& oldValues, const Array::Ptr& newValues);
void SendTimePeriodExcludesChanged(const TimePeriod::Ptr& timeperiod, const Array::Ptr& oldValues, const Array::Ptr& newValues);
template<class T>
void SendGroupsChanged(const ConfigObject::Ptr& command, const Array::Ptr& oldValues, const Array::Ptr& newValues);
void SendCommandEnvChanged(const ConfigObject::Ptr& command, const CmdArgEnvRedisKeys& cmdRedisKeys, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues);
void SendCommandArgumentsChanged(const ConfigObject::Ptr& command, const CmdArgEnvRedisKeys& cmdRedisKeys, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues);
void SendCustomVarsChanged(const ConfigObject::Ptr& object, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues);
void ForwardHistoryEntries();
Dictionary::Ptr SerializeState(const Checkable::Ptr& checkable);
/* Stats */
static Dictionary::Ptr GetStats();
/* utilities */
static void DeleteKeys(const RedisConnection::Ptr& conn, const std::vector<RedisConnection::QueryArg>& keys);
static std::vector<RedisConnection::QueryArg> GetTypeOverwriteKeys(const Type::Ptr& type, bool skipChecksums = false);
static void AddDataToHmSets(std::map<RedisConnection::QueryArg, RedisConnection::Query>& hMSets, const RedisConnection::QueryArg& redisKey, const String& id, const Dictionary::Ptr& data);
static bool IsStateKey(const RedisConnection::QueryArg& key);
static String FormatCheckSumBinary(const String& str);
static String FormatCommandLine(const Value& commandLine);
static QueryArgPair GetCheckableStateKeys(const Type::Ptr& type);
static CmdArgEnvRedisKeys GetCmdEnvArgKeys(const Type::Ptr& cmdType);
static long long TimestampToMilliseconds(double timestamp);
static String IcingaToStreamValue(const Value& value);
static std::vector<Value> GetArrayDeletedValues(const Array::Ptr& arrayOld, const Array::Ptr& arrayNew);
static std::vector<String> GetDictionaryDeletedKeys(const Dictionary::Ptr& dictOld, const Dictionary::Ptr& dictNew);
static String GetObjectIdentifier(const ConfigObject::Ptr& object);
static String CalcEventID(const char* eventType, const ConfigObject::Ptr& object, double eventTime = 0, NotificationType nt = NotificationType(0));
static int StateFilterToRedisValue(int filter);
static int TypeFilterToRedisValue(int filter);
static const char* GetNotificationTypeByEnum(NotificationType type);
static String CommentTypeToString(CommentType 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 GetDependencyEdgeStateId(const DependencyGroup::Ptr& dependencyGroup, const Dependency::Ptr& dep);
static String HashValue(const Value& value);
static String HashValue(const Value& value, const std::set<String>& propertiesBlacklist, bool propertiesWhitelist = false);
static String GetLowerCaseTypeNameDB(const ConfigObject::Ptr& obj);
static bool PrepareObject(const ConfigObject::Ptr& object, Dictionary::Ptr& attributes);
static void ReachabilityChangeHandler(const std::set<Checkable::Ptr>& children);
static void StateChangeHandler(const ConfigObject::Ptr& object, const CheckResult::Ptr& cr, StateType type);
static void VersionChangedHandler(const ConfigObject::Ptr& object);
static void DowntimeStartedHandler(const Downtime::Ptr& downtime);
static void DowntimeRemovedHandler(const Downtime::Ptr& downtime);
static void NotificationSentToAllUsersHandler(
const Notification::Ptr& notification, const Checkable::Ptr& checkable, const std::set<User::Ptr>& users,
NotificationType type, const CheckResult::Ptr& cr, const String& author, const String& text
);
static void CommentAddedHandler(const Comment::Ptr& comment);
static void CommentRemovedHandler(const Comment::Ptr& comment);
static void FlappingChangeHandler(const Checkable::Ptr& checkable, double changeTime);
static void NewCheckResultHandler(const Checkable::Ptr& checkable);
static void NextCheckChangedHandler(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);
static void NotificationUsersChangedHandler(const Notification::Ptr& notification, const Array::Ptr& oldValues, const Array::Ptr& newValues);
static void NotificationUserGroupsChangedHandler(const Notification::Ptr& notification, const Array::Ptr& oldValues, const Array::Ptr& newValues);
static void TimePeriodRangesChangedHandler(const TimePeriod::Ptr& timeperiod, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues);
static void TimePeriodIncludesChangedHandler(const TimePeriod::Ptr& timeperiod, const Array::Ptr& oldValues, const Array::Ptr& newValues);
static void TimePeriodExcludesChangedHandler(const TimePeriod::Ptr& timeperiod, const Array::Ptr& oldValues, const Array::Ptr& newValues);
static void UserGroupsChangedHandler(const User::Ptr& user, const Array::Ptr&, const Array::Ptr& newValues);
static void HostGroupsChangedHandler(const Host::Ptr& host, const Array::Ptr& oldValues, const Array::Ptr& newValues);
static void ServiceGroupsChangedHandler(const Service::Ptr& service, const Array::Ptr& oldValues, const Array::Ptr& newValues);
static void CommandEnvChangedHandler(const Command::Ptr& command, const Dictionary::Ptr& oldValues, const Dictionary::Ptr& newValues);
static void CommandArgumentsChangedHandler(const Command::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<RedisConnection::QueryArg, RedisConnection::Query>& hMSets, const std::vector<Dictionary::Ptr>& runtimeUpdates);
void AssertOnWorkQueue();
void ExceptionHandler(std::exception_ptr exp);
using SyncableTypeInfo = std::pair<const Type::Ptr, QueryArgPair>;
static std::vector<SyncableTypeInfo> GetSyncableTypes();
static const QueryArgPair& GetSyncableTypeRedisKeys(const Type::Ptr& type);
static void InitEnvironmentId();
static void PersistEnvironmentId();
Timer::Ptr m_StatsTimer;
WorkQueue m_WorkQueue{0, 1, LogNotice};
std::future<void> m_HistoryThread;
Bulker<RedisConnection::Query> m_HistoryBulker {4096, std::chrono::milliseconds(250)};
bool m_ConfigDumpInProgress;
std::atomic_bool m_ConfigDumpDone{false};
/**
* The primary Redis connection used to send history and heartbeat queries.
*
* This connection is used exclusively for sending history and heartbeat queries to Redis. It ensures that
* history and heartbeat operations do not interfere with other Redis operations. Also, it is the leader for
* all other Redis connections including @c m_RconWorker, and is the only source of truth for all IcingaDB Redis
* related connection statistics.
*
* Note: This will still be shared with the icingadb check command, as that command also sends
* only XREAD queries which are similar in nature to history/heartbeat queries.
*/
RedisConnection::Ptr m_Rcon;
// m_RconLocked contains a copy of the value in m_Rcon where all accesses are guarded by a mutex to
// allow safe concurrent access like from the icingadb check command. It's a copy to still allow fast access
// without additional synchronization to m_Rcon within the IcingaDB feature itself.
Locked<RedisConnection::Ptr> m_RconLocked;
/**
* A Redis connection for config and state updates.
*
* This connection is used for all non-history and non-heartbeat related queries to Redis.
* It is a child of @c m_Rcon, meaning it forwards all its connection stats to @c m_Rcon as well.
*/
RedisConnection::Ptr m_RconWorker;
std::unordered_map<ConfigType*, RedisConnection::Ptr> m_Rcons;
std::atomic_size_t m_PendingRcons;
struct {
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
// to have multiple ones). It is initialized once (synchronized using m_EnvironmentIdInitMutex). After successful
// initialization, the value is read-only and can be accessed without further synchronization.
static String m_EnvironmentId;
static std::mutex m_EnvironmentIdInitMutex;
std::thread m_PendingItemsThread; // The background worker thread (consumer of m_PendingItems).
icingadb::task_queue::PendingItemsSet m_PendingItems; // Container for pending items with dirty bits (access protected by m_PendingItemsMutex).
mutable std::mutex m_PendingItemsMutex; // Mutex to protect access to m_PendingItems.
std::condition_variable m_PendingItemsCV; // Condition variable to forcefully wake up the worker thread.
void PendingItemsThreadProc();
void ProcessQueueItem(const icingadb::task_queue::PendingConfigItem& item);
void ProcessQueueItem(const icingadb::task_queue::PendingDependencyGroupStateItem& item) const;
void ProcessQueueItem(const icingadb::task_queue::PendingDependencyEdgeItem& item);
void ProcessQueueItem(const icingadb::task_queue::RelationsDeletionItem& item);
void EnqueueConfigObject(const ConfigObject::Ptr& object, uint32_t bits);
void EnqueueDependencyGroupStateUpdate(const DependencyGroup::Ptr& depGroup);
void EnqueueDependencyChildRegistered(const DependencyGroup::Ptr& depGroup, const Checkable::Ptr& child);
void EnqueueDependencyChildRemoved(const DependencyGroup::Ptr& depGroup, const std::vector<Dependency::Ptr>& dependencies, bool removeGroup);
void EnqueueRelationsDeletion(const String& id, icingadb::task_queue::RelationsDeletionItem::RelationsKeySet relations);
};
}
#endif /* ICINGADB_H */