Skip to content

Commit

Permalink
Merge pull request #9545 from Icinga/targeted-apply-rules
Browse files Browse the repository at this point in the history
Separately handle apply rules targetting only specific parent objects
  • Loading branch information
julianbrost authored Nov 4, 2022
2 parents 08324f8 + a8d46e6 commit 98902b2
Show file tree
Hide file tree
Showing 15 changed files with 438 additions and 59 deletions.
5 changes: 5 additions & 0 deletions lib/base/string.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -461,3 +461,8 @@ String::ConstIterator icinga::range_end(const String& x)
{
return x.End();
}

std::size_t std::hash<String>::operator()(const String& s) const noexcept
{
return std::hash<std::string>{}(s.GetData());
}
7 changes: 7 additions & 0 deletions lib/base/string.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "base/object.hpp"
#include <boost/range/iterator.hpp>
#include <boost/utility/string_view.hpp>
#include <functional>
#include <string>
#include <iosfwd>

Expand Down Expand Up @@ -178,6 +179,12 @@ String::ConstIterator range_end(const String& x);

}

template<>
struct std::hash<icinga::String>
{
std::size_t operator()(const icinga::String& s) const noexcept;
};

extern template class std::vector<icinga::String>;

namespace boost
Expand Down
2 changes: 1 addition & 1 deletion lib/config/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
254 changes: 254 additions & 0 deletions lib/config/applyrule-targeted.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
/* Icinga 2 | (c) 2022 Icinga GmbH | GPLv2+ */

#include "base/string.hpp"
#include "config/applyrule.hpp"
#include "config/expression.hpp"
#include <utility>
#include <vector>

using namespace icinga;

/**
* @returns All ApplyRules targeting only specific parent objects including the given host. (See AddTargetedRule().)
*/
const std::set<ApplyRule::Ptr>& ApplyRule::GetTargetedHostRules(const Type::Ptr& sourceType, const String& host)
{
auto perSourceType (m_Rules.find(sourceType.get()));

if (perSourceType != m_Rules.end()) {
auto perHost (perSourceType->second.Targeted.find(host));

if (perHost != perSourceType->second.Targeted.end()) {
return perHost->second.ForHost;
}
}

static const std::set<ApplyRule::Ptr> noRules;
return noRules;
}

/**
* @returns All ApplyRules targeting only specific parent objects including the given service. (See AddTargetedRule().)
*/
const std::set<ApplyRule::Ptr>& ApplyRule::GetTargetedServiceRules(const Type::Ptr& sourceType, const String& host, const String& service)
{
auto perSourceType (m_Rules.find(sourceType.get()));

if (perSourceType != m_Rules.end()) {
auto perHost (perSourceType->second.Targeted.find(host));

if (perHost != perSourceType->second.Targeted.end()) {
auto perService (perHost->second.ForServices.find(service));

if (perService != perHost->second.ForServices.end()) {
return perService->second;
}
}
}

static const std::set<ApplyRule::Ptr> 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(const ApplyRule::Ptr& rule, const String& targetType, ApplyRule::PerSourceType& rules)
{
if (targetType == "Host") {
std::vector<const String *> hosts;

if (GetTargetHosts(rule->m_Filter.get(), hosts)) {
for (auto host : hosts) {
rules.Targeted[*host].ForHost.emplace(rule);
}

return true;
}
} else if (targetType == "Service") {
std::vector<std::pair<const String *, const String *>> services;

if (GetTargetServices(rule->m_Filter.get(), services)) {
for (auto service : services) {
rules.Targeted[*service.first].ForServices[*service.second].emplace(rule);
}

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<const String *>& hosts)
{
auto lor (dynamic_cast<LogicalOrExpression*>(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<std::pair<const String *, const String *>>& services)
{
auto lor (dynamic_cast<LogicalOrExpression*>(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<const String *, const String *> ApplyRule::GetTargetService(Expression* assignFilter)
{
auto land (dynamic_cast<LogicalAndExpression*>(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<EqualExpression*>(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<IndexerExpression*>(exp));

if (!ixr) {
return false;
}

auto var (dynamic_cast<VariableExpression*>(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<LiteralExpression*>(exp));

if (!lit) {
return nullptr;
}

auto& val (lit->GetValue());

if (!val.IsString()) {
return nullptr;
}

return &val.Get<String>();
}
51 changes: 39 additions & 12 deletions lib/config/applyrule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include "config/applyrule.hpp"
#include "base/logger.hpp"
#include <set>
#include <unordered_set>

using namespace icinga;

Expand Down Expand Up @@ -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::Ptr rule = new ApplyRule(name, expression, filter, package, fkvar, fvvar, fterm, ignoreOnError, di, scope);
auto& rules (m_Rules[Type::GetByName(sourceType).get()]);

if (!AddTargetedRule(rule, *actualTargetType, rules)) {
rules.Regular[Type::GetByName(*actualTargetType).get()].emplace_back(std::move(rule));
}
}

bool ApplyRule::EvaluateFilter(ScriptFrame& frame) const
Expand Down Expand Up @@ -140,33 +144,56 @@ bool ApplyRule::HasMatches() const
return m_HasMatches;
}

std::vector<ApplyRule>& ApplyRule::GetRules(const Type::Ptr& sourceType, const Type::Ptr& targetType)
const std::vector<ApplyRule::Ptr>& ApplyRule::GetRules(const Type::Ptr& sourceType, const Type::Ptr& targetType)
{
auto perSourceType (m_Rules.find(sourceType.get()));

if (perSourceType != m_Rules.end()) {
auto perTargetType (perSourceType->second.find(targetType.get()));
auto perTargetType (perSourceType->second.Regular.find(targetType.get()));

if (perTargetType != perSourceType->second.end()) {
if (perTargetType != perSourceType->second.Regular.end()) {
return perTargetType->second;
}
}

static std::vector<ApplyRule> noRules;
static const std::vector<ApplyRule::Ptr> noRules;
return noRules;
}

void ApplyRule::CheckMatches(bool silent)
{
for (auto& perSourceType : m_Rules) {
for (auto& perTargetType : perSourceType.second) {
for (auto& perTargetType : perSourceType.second.Regular) {
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!";
CheckMatches(rule, perSourceType.first, silent);
}
}

std::unordered_set<ApplyRule*> targeted;

for (auto& perHost : perSourceType.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::Ptr& 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!";
}
}
Loading

0 comments on commit 98902b2

Please sign in to comment.