diff --git a/lib/config/CMakeLists.txt b/lib/config/CMakeLists.txt index 042668dc3..80b8c2c44 100644 --- a/lib/config/CMakeLists.txt +++ b/lib/config/CMakeLists.txt @@ -21,7 +21,7 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}) set(config_SOURCES i2-config.hpp activationcontext.cpp activationcontext.hpp - applyrule.cpp applyrule.hpp + applyrule.cpp applyrule-targeted.cpp applyrule.hpp configcompiler.cpp configcompiler.hpp configcompilercontext.cpp configcompilercontext.hpp configfragment.hpp diff --git a/lib/config/applyrule-targeted.cpp b/lib/config/applyrule-targeted.cpp new file mode 100644 index 000000000..1a2ce564e --- /dev/null +++ b/lib/config/applyrule-targeted.cpp @@ -0,0 +1,266 @@ +/* Icinga 2 | (c) 2022 Icinga GmbH | GPLv2+ */ + +#include "base/string.hpp" +#include "config/applyrule.hpp" +#include "config/expression.hpp" +#include +#include + +using namespace icinga; + +/** + * @returns All ApplyRules targeting only specific parent objects including the given host. (See AddTargetedRule().) + */ +const std::vector& ApplyRule::GetTargetedHostRules(const Type::Ptr& sourceType, const Type::Ptr& targetType, const String& host) +{ + auto perSourceType (m_Rules.find(sourceType.get())); + + if (perSourceType != m_Rules.end()) { + auto perTargetType (perSourceType->second.find(targetType.get())); + + if (perTargetType != perSourceType->second.end()) { + auto perHost (perTargetType->second.Targeted.find(host)); + + if (perHost != perTargetType->second.Targeted.end()) { + return perHost->second.ForHost; + } + } + } + + static const std::vector noRules; + return noRules; +} + +/** + * @returns All ApplyRules targeting only specific parent objects including the given service. (See AddTargetedRule().) + */ +const std::vector& ApplyRule::GetTargetedServiceRules(const Type::Ptr& sourceType, const Type::Ptr& targetType, const String& host, const String& service) +{ + auto perSourceType (m_Rules.find(sourceType.get())); + + if (perSourceType != m_Rules.end()) { + auto perTargetType (perSourceType->second.find(targetType.get())); + + if (perTargetType != perSourceType->second.end()) { + auto perHost (perTargetType->second.Targeted.find(host)); + + if (perHost != perTargetType->second.Targeted.end()) { + auto perService (perHost->second.ForServices.find(service)); + + if (perService != perHost->second.ForServices.end()) { + return perService->second; + } + } + } + } + + static const std::vector noRules; + return noRules; +} + +/** + * If the given ApplyRule targets only specific parent objects, add it to the respective "index". + * + * - The above means for apply T "N" to Host: assign where host.name == "H" [ || host.name == "h" ... ] + * - For apply T "N" to Service it means: assign where host.name == "H" && service.name == "S" [ || host.name == "h" && service.name == "s" ... ] + * + * The order of operands of || && == doesn't matter. + * + * @returns Whether the rule has been added to the "index". + */ +bool ApplyRule::AddTargetedRule(ApplyRule& rule, const String& sourceType, const String& targetType, ApplyRule::PerTypes& rules) +{ + if (targetType == "Host") { + std::vector hosts; + + if (GetTargetHosts(rule.m_Filter.get(), hosts)) { + ApplyRule::Ptr sharedRule = new ApplyRule(std::move(rule)); + + for (auto host : hosts) { + rules.Targeted[*host].ForHost.emplace_back(sharedRule); + } + + return true; + } + } else if (targetType == "Service") { + std::vector> services; + + if (GetTargetServices(rule.m_Filter.get(), services)) { + ApplyRule::Ptr sharedRule = new ApplyRule(std::move(rule)); + + for (auto service : services) { + rules.Targeted[*service.first].ForServices[*service.second].emplace_back(sharedRule); + } + + return true; + } + } + + return false; +} + +/** + * If the given assign filter is like the following, extract the host names ("H", "h", ...) into the vector: + * + * host.name == "H" [ || host.name == "h" ... ] + * + * The order of operands of || == doesn't matter. + * + * @returns Whether the given assign filter is like above. + */ +bool ApplyRule::GetTargetHosts(Expression* assignFilter, std::vector& hosts) +{ + auto lor (dynamic_cast(assignFilter)); + + if (lor) { + return GetTargetHosts(lor->GetOperand1().get(), hosts) + && GetTargetHosts(lor->GetOperand2().get(), hosts); + } + + auto name (GetComparedName(assignFilter, "host")); + + if (name) { + hosts.emplace_back(name); + return true; + } + + return false; +} + +/** + * If the given assign filter is like the following, extract the host+service names ("H"+"S", "h"+"s", ...) into the vector: + * + * host.name == "H" && service.name == "S" [ || host.name == "h" && service.name == "s" ... ] + * + * The order of operands of || && == doesn't matter. + * + * @returns Whether the given assign filter is like above. + */ +bool ApplyRule::GetTargetServices(Expression* assignFilter, std::vector>& services) +{ + auto lor (dynamic_cast(assignFilter)); + + if (lor) { + return GetTargetServices(lor->GetOperand1().get(), services) + && GetTargetServices(lor->GetOperand2().get(), services); + } + + auto service (GetTargetService(assignFilter)); + + if (service.first) { + services.emplace_back(service); + return true; + } + + return false; +} + +/** + * If the given filter is like the following, extract the host+service names ("H"+"S"): + * + * host.name == "H" && service.name == "S" + * + * The order of operands of && == doesn't matter. + * + * @returns {host, service} on success and {nullptr, nullptr} on failure. + */ +std::pair ApplyRule::GetTargetService(Expression* assignFilter) +{ + auto land (dynamic_cast(assignFilter)); + + if (!land) { + return {nullptr, nullptr}; + } + + auto op1 (land->GetOperand1().get()); + auto op2 (land->GetOperand2().get()); + auto host (GetComparedName(op1, "host")); + + if (!host) { + std::swap(op1, op2); + host = GetComparedName(op1, "host"); + } + + if (host) { + auto service (GetComparedName(op2, "service")); + + if (service) { + return {host, service}; + } + } + + return {nullptr, nullptr}; +} + +/** + * If the given filter is like the following, extract the object name ("N"): + * + * $lcType$.name == "N" + * + * The order of operands of == doesn't matter. + * + * @returns The object name on success and nullptr on failure. + */ +const String * ApplyRule::GetComparedName(Expression* assignFilter, const char * lcType) +{ + auto eq (dynamic_cast(assignFilter)); + + if (!eq) { + return nullptr; + } + + auto op1 (eq->GetOperand1().get()); + auto op2 (eq->GetOperand2().get()); + + if (IsNameIndexer(op1, lcType)) { + return GetLiteralStringValue(op2); + } + + if (IsNameIndexer(op2, lcType)) { + return GetLiteralStringValue(op1); + } + + return nullptr; +} + +/** + * @returns Whether the given expression is like $lcType$.name. + */ +bool ApplyRule::IsNameIndexer(Expression* exp, const char * lcType) +{ + auto ixr (dynamic_cast(exp)); + + if (!ixr) { + return false; + } + + auto var (dynamic_cast(ixr->GetOperand1().get())); + + if (!var || var->GetVariable() != lcType) { + return false; + } + + auto val (GetLiteralStringValue(ixr->GetOperand2().get())); + + return val && *val == "name"; +} + +/** + * @returns If the given expression is a string literal, the string. nullptr on failure. + */ +const String * ApplyRule::GetLiteralStringValue(Expression* exp) +{ + auto lit (dynamic_cast(exp)); + + if (!lit) { + return nullptr; + } + + auto& val (lit->GetValue()); + + if (!val.IsString()) { + return nullptr; + } + + return &val.Get(); +} diff --git a/lib/config/applyrule.cpp b/lib/config/applyrule.cpp index 363dce90b..cd268269e 100644 --- a/lib/config/applyrule.cpp +++ b/lib/config/applyrule.cpp @@ -3,6 +3,7 @@ #include "config/applyrule.hpp" #include "base/logger.hpp" #include +#include using namespace icinga; @@ -80,9 +81,12 @@ void ApplyRule::AddRule(const String& sourceType, const String& targetType, cons } } - m_Rules[Type::GetByName(sourceType).get()][Type::GetByName(*actualTargetType).get()].emplace_back(ApplyRule( - name, expression, filter, package, fkvar, fvvar, fterm, ignoreOnError, di, scope - )); + ApplyRule rule (name, expression, filter, package, fkvar, fvvar, fterm, ignoreOnError, di, scope); + auto& rules (m_Rules[Type::GetByName(sourceType).get()][Type::GetByName(*actualTargetType).get()]); + + if (!AddTargetedRule(rule, sourceType, *actualTargetType, rules)) { + rules.Regular.emplace_back(std::move(rule)); + } } bool ApplyRule::EvaluateFilter(ScriptFrame& frame) const @@ -148,7 +152,7 @@ std::vector& ApplyRule::GetRules(const Type::Ptr& sourceType, const T auto perTargetType (perSourceType->second.find(targetType.get())); if (perTargetType != perSourceType->second.end()) { - return perTargetType->second; + return perTargetType->second.Regular; } } @@ -160,13 +164,36 @@ void ApplyRule::CheckMatches(bool silent) { for (auto& perSourceType : m_Rules) { for (auto& perTargetType : perSourceType.second) { - for (auto& rule : perTargetType.second) { - if (!rule.HasMatches() && !silent) { - Log(LogWarning, "ApplyRule") - << "Apply rule '" << rule.GetName() << "' (" << rule.GetDebugInfo() << ") for type '" - << perSourceType.first->GetName() << "' does not match anywhere!"; + for (auto& rule : perTargetType.second.Regular) { + CheckMatches(rule, perSourceType.first, silent); + } + + std::unordered_set targeted; + + for (auto& perHost : perTargetType.second.Targeted) { + for (auto& rule : perHost.second.ForHost) { + targeted.emplace(rule.get()); } + + for (auto& perService : perHost.second.ForServices) { + for (auto& rule : perService.second) { + targeted.emplace(rule.get()); + } + } + } + + for (auto rule : targeted) { + CheckMatches(*rule, perSourceType.first, silent); } } } } + +void ApplyRule::CheckMatches(const ApplyRule& rule, Type* sourceType, bool silent) +{ + if (!rule.HasMatches() && !silent) { + Log(LogWarning, "ApplyRule") + << "Apply rule '" << rule.GetName() << "' (" << rule.GetDebugInfo() << ") for type '" + << sourceType->GetName() << "' does not match anywhere!"; + } +} diff --git a/lib/config/applyrule.hpp b/lib/config/applyrule.hpp index 5001d26cd..37ec04cfd 100644 --- a/lib/config/applyrule.hpp +++ b/lib/config/applyrule.hpp @@ -21,8 +21,35 @@ class ApplyRule : public SharedObject public: DECLARE_PTR_TYPEDEFS(ApplyRule); + struct PerHost + { + std::vector ForHost; + std::unordered_map> ForServices; + }; + + struct PerTypes + { + std::vector Regular; + std::unordered_map Targeted; + }; + + /* + * m_Rules[T::TypeInstance.get()][Host::TypeInstance.get()].Targeted["H"].ForHost + * contains all apply rules like apply T "x" to Host { ... } + * which target only specific hosts incl. "H", e. g. via + * assign where host.name == "H" || host.name == "h". + * + * m_Rules[T::TypeInstance.get()][Service::TypeInstance.get()].Targeted["H"].ForServices["S"] + * contains all apply rules like apply T "x" to Service { ... } + * which target only specific services on specific hosts incl. "H!S", + * e. g. via assign where host.name == "H" && service.name == "S". + * + * m_Rules[T::TypeInstance.get()][C::TypeInstance.get()].Regular + * contains all other apply rules like apply T "x" to C { ... }. + */ + typedef std::unordered_map> RuleMap; + typedef std::map > TypeMap; - typedef std::unordered_map>> RuleMap; String GetName() const; Expression::Ptr GetExpression() const; @@ -43,6 +70,8 @@ public: const Expression::Ptr& filter, const String& package, const String& fkvar, const String& fvvar, const Expression::Ptr& fterm, bool ignoreOnError, const DebugInfo& di, const Dictionary::Ptr& scope); static std::vector& GetRules(const Type::Ptr& sourceType, const Type::Ptr& targetType); + static const std::vector& GetTargetedHostRules(const Type::Ptr& sourceType, const Type::Ptr& targetType, const String& host); + static const std::vector& GetTargetedServiceRules(const Type::Ptr& sourceType, const Type::Ptr& targetType, const String& host, const String& service); static void RegisterType(const String& sourceType, const std::vector& targetTypes); static bool IsValidSourceType(const String& sourceType); @@ -50,6 +79,7 @@ public: static const std::vector& GetTargetTypes(const String& sourceType); static void CheckMatches(bool silent); + static void CheckMatches(const ApplyRule& rule, Type* sourceType, bool silent); private: String m_Name; @@ -67,6 +97,14 @@ private: static TypeMap m_Types; static RuleMap m_Rules; + static bool AddTargetedRule(ApplyRule& rule, const String& sourceType, const String& targetType, ApplyRule::PerTypes& rules); + static bool GetTargetHosts(Expression* assignFilter, std::vector& hosts); + static bool GetTargetServices(Expression* assignFilter, std::vector>& services); + static std::pair GetTargetService(Expression* assignFilter); + static const String * GetComparedName(Expression* assignFilter, const char * lcType); + static bool IsNameIndexer(Expression* exp, const char * lcType); + static const String * GetLiteralStringValue(Expression* exp); + ApplyRule(String name, Expression::Ptr expression, Expression::Ptr filter, String package, String fkvar, String fvvar, Expression::Ptr fterm, bool ignoreOnError, DebugInfo di, Dictionary::Ptr scope); diff --git a/lib/config/expression.hpp b/lib/config/expression.hpp index 21ab7a2e6..26ffb341b 100644 --- a/lib/config/expression.hpp +++ b/lib/config/expression.hpp @@ -283,6 +283,16 @@ public: : DebuggableExpression(debugInfo), m_Operand1(std::move(operand1)), m_Operand2(std::move(operand2)) { } + inline const std::unique_ptr& GetOperand1() const noexcept + { + return m_Operand1; + } + + inline const std::unique_ptr& GetOperand2() const noexcept + { + return m_Operand2; + } + protected: std::unique_ptr m_Operand1; std::unique_ptr m_Operand2; diff --git a/lib/icinga/dependency-apply.cpp b/lib/icinga/dependency-apply.cpp index 07ab31254..7b9503fb9 100644 --- a/lib/icinga/dependency-apply.cpp +++ b/lib/icinga/dependency-apply.cpp @@ -140,6 +140,11 @@ void Dependency::EvaluateApplyRules(const Host::Ptr& host) if (EvaluateApplyRule(host, rule)) rule.AddMatch(); } + + for (auto& rule : ApplyRule::GetTargetedHostRules(Dependency::TypeInstance, Host::TypeInstance, host->GetName())) { + if (EvaluateApplyRule(host, *rule)) + rule->AddMatch(); + } } void Dependency::EvaluateApplyRules(const Service::Ptr& service) @@ -150,4 +155,9 @@ void Dependency::EvaluateApplyRules(const Service::Ptr& service) if (EvaluateApplyRule(service, rule)) rule.AddMatch(); } + + for (auto& rule : ApplyRule::GetTargetedServiceRules(Dependency::TypeInstance, Service::TypeInstance, service->GetHost()->GetName(), service->GetName())) { + if (EvaluateApplyRule(service, *rule)) + rule->AddMatch(); + } } diff --git a/lib/icinga/notification-apply.cpp b/lib/icinga/notification-apply.cpp index 61d219215..d5d3d4870 100644 --- a/lib/icinga/notification-apply.cpp +++ b/lib/icinga/notification-apply.cpp @@ -140,6 +140,11 @@ void Notification::EvaluateApplyRules(const Host::Ptr& host) if (EvaluateApplyRule(host, rule)) rule.AddMatch(); } + + for (auto& rule : ApplyRule::GetTargetedHostRules(Notification::TypeInstance, Host::TypeInstance, host->GetName())) { + if (EvaluateApplyRule(host, *rule)) + rule->AddMatch(); + } } void Notification::EvaluateApplyRules(const Service::Ptr& service) @@ -150,4 +155,9 @@ void Notification::EvaluateApplyRules(const Service::Ptr& service) if (EvaluateApplyRule(service, rule)) rule.AddMatch(); } + + for (auto& rule : ApplyRule::GetTargetedServiceRules(Notification::TypeInstance, Service::TypeInstance, service->GetHost()->GetName(), service->GetName())) { + if (EvaluateApplyRule(service, *rule)) + rule->AddMatch(); + } } diff --git a/lib/icinga/scheduleddowntime-apply.cpp b/lib/icinga/scheduleddowntime-apply.cpp index 0dfaf1b09..893c42aca 100644 --- a/lib/icinga/scheduleddowntime-apply.cpp +++ b/lib/icinga/scheduleddowntime-apply.cpp @@ -138,6 +138,11 @@ void ScheduledDowntime::EvaluateApplyRules(const Host::Ptr& host) if (EvaluateApplyRule(host, rule)) rule.AddMatch(); } + + for (auto& rule : ApplyRule::GetTargetedHostRules(ScheduledDowntime::TypeInstance, Host::TypeInstance, host->GetName())) { + if (EvaluateApplyRule(host, *rule)) + rule->AddMatch(); + } } void ScheduledDowntime::EvaluateApplyRules(const Service::Ptr& service) @@ -148,4 +153,9 @@ void ScheduledDowntime::EvaluateApplyRules(const Service::Ptr& service) if (EvaluateApplyRule(service, rule)) rule.AddMatch(); } + + for (auto& rule : ApplyRule::GetTargetedServiceRules(ScheduledDowntime::TypeInstance, Service::TypeInstance, service->GetHost()->GetName(), service->GetName())) { + if (EvaluateApplyRule(service, *rule)) + rule->AddMatch(); + } } diff --git a/lib/icinga/service-apply.cpp b/lib/icinga/service-apply.cpp index 24a19c956..e14342948 100644 --- a/lib/icinga/service-apply.cpp +++ b/lib/icinga/service-apply.cpp @@ -127,4 +127,9 @@ void Service::EvaluateApplyRules(const Host::Ptr& host) if (EvaluateApplyRule(host, rule)) rule.AddMatch(); } + + for (auto& rule : ApplyRule::GetTargetedHostRules(Service::TypeInstance, Host::TypeInstance, host->GetName())) { + if (EvaluateApplyRule(host, *rule)) + rule->AddMatch(); + } }