diff --git a/lib/base/configtype.hpp b/lib/base/configtype.hpp index c77fc5ec2..f709b2c0d 100644 --- a/lib/base/configtype.hpp +++ b/lib/base/configtype.hpp @@ -9,11 +9,13 @@ #include "base/dictionary.hpp" #include #include +#include namespace icinga { class ConfigObject; +class ConfigItems; class ConfigType { @@ -48,6 +50,13 @@ for (const auto& object : objects) { int GetObjectCount() const; + /** + * Signal that allows hooking into the config loading process just before ConfigObject::OnAllConfigLoaded() is + * called for a bunch of objects. A vector of pointers to these objects is passed as an argument. All elements + * are of the object type the signal is called on. + */ + boost::signals2::signal BeforeOnAllConfigLoaded; + private: typedef std::unordered_map > ObjectMap; typedef std::vector > ObjectVector; diff --git a/lib/config/configitem.cpp b/lib/config/configitem.cpp index e8a509275..5dce19eda 100644 --- a/lib/config/configitem.cpp +++ b/lib/config/configitem.cpp @@ -499,6 +499,23 @@ bool ConfigItem::CommitNewItems(const ActivationContext::Ptr& context, WorkQueue auto items (itemsByType.find(type.get())); if (items != itemsByType.end()) { + auto configType = dynamic_cast(type.get()); + + // Skip the call if no handlers are connected (signal::empty()) or there are no items (vector::empty()). + if (configType && !configType->BeforeOnAllConfigLoaded.empty() && !items->second.empty()) { + // Call the signal in the WorkQueue so that if an exception is thrown, it is caught by the WorkQueue + // and then reported like any other config validation error. + upq.Enqueue([&configType, &items]() { + configType->BeforeOnAllConfigLoaded(ConfigItems(items->second)); + }); + + upq.Join(); + + if (upq.HasExceptions()) { + return false; + } + } + upq.ParallelFor(items->second, [¬ified_items](const ItemPair& ip) { const ConfigItem::Ptr& item = ip.first; @@ -525,6 +542,10 @@ bool ConfigItem::CommitNewItems(const ActivationContext::Ptr& context, WorkQueue }); upq.Join(); + + if (upq.HasExceptions()) { + return false; + } } } @@ -534,9 +555,6 @@ bool ConfigItem::CommitNewItems(const ActivationContext::Ptr& context, WorkQueue << "Sent OnAllConfigLoaded to " << notified_items << " items of type '" << type->GetName() << "'."; #endif /* I2_DEBUG */ - if (upq.HasExceptions()) - return false; - notified_items = 0; for (auto loadDep : type->GetLoadDependencies()) { auto items (itemsByType.find(loadDep)); diff --git a/lib/config/configitem.hpp b/lib/config/configitem.hpp index b99cd08e5..007a3c08a 100644 --- a/lib/config/configitem.hpp +++ b/lib/config/configitem.hpp @@ -101,6 +101,39 @@ private: static bool CommitNewItems(const ActivationContext::Ptr& context, WorkQueue& upq, std::vector& newItems); }; +/** + * Helper class for exposing config items being committed to the ConfigType::BeforeOnAllConfigLoaded callback. + * + * This class wraps a reference to an internal data structure used in ConfigItem::CommitNewItems() and provides + * functions useful for the callbacks without exposing the internals of CommitNewItems(). + */ +class ConfigItems +{ + explicit ConfigItems(std::vector>& items) : m_Items(items) {} + + std::vector>& m_Items; + + friend ConfigItem; + +public: + /** + * ForEachObject(f) calls f(t) for each object T::Ptr t in vector of underlying config items. + * + * @tparam T ConfigObject type to iterate over + * @tparam F Callback functor type (usually automatically deduced from func) + * @param func Functor accepting T::Ptr as an argument to be called for each object + */ + template + void ForEachObject(F func) const + { + for (const auto& item : m_Items) { + if (typename T::Ptr obj = dynamic_pointer_cast(item.first->GetObject()); obj) { + func(std::move(obj)); + } + } + } +}; + } #endif /* CONFIGITEM_H */