diff --git a/lib/icinga/checkable.cpp b/lib/icinga/checkable.cpp index a53ab6d13..b82bbfb7d 100644 --- a/lib/icinga/checkable.cpp +++ b/lib/icinga/checkable.cpp @@ -329,3 +329,12 @@ String Checkable::StateTypeToString(StateType type) return type == StateTypeSoft ? "SOFT" : "HARD"; } +Dictionary::Ptr Checkable::GetFrozenLocalsForApply() +{ + if (!m_FrozenLocalsForApply) { + m_FrozenLocalsForApply = MakeLocalsForApply(); + m_FrozenLocalsForApply->Freeze(); + } + + return m_FrozenLocalsForApply; +} diff --git a/lib/icinga/checkable.hpp b/lib/icinga/checkable.hpp index bd539c4a3..1c110e892 100644 --- a/lib/icinga/checkable.hpp +++ b/lib/icinga/checkable.hpp @@ -217,12 +217,18 @@ public: static int GetPendingChecks(); static void AquirePendingCheckSlot(int maxPendingChecks); + Dictionary::Ptr GetFrozenLocalsForApply(); + protected: void Start(bool runtimeCreated) override; void OnConfigLoaded() override; void OnAllConfigLoaded() override; + virtual Dictionary::Ptr MakeLocalsForApply() = 0; + private: + Dictionary::Ptr m_FrozenLocalsForApply; + mutable std::mutex m_CheckableMutex; bool m_CheckRunning{false}; long m_SchedulingOffset; diff --git a/lib/icinga/dependency-apply.cpp b/lib/icinga/dependency-apply.cpp index 87f4ad222..bb860a4ca 100644 --- a/lib/icinga/dependency-apply.cpp +++ b/lib/icinga/dependency-apply.cpp @@ -69,63 +69,66 @@ bool Dependency::EvaluateApplyRule(const Checkable::Ptr& checkable, const ApplyR CONTEXT("Evaluating 'apply' rule (" << di << ")"); - Host::Ptr host; - Service::Ptr service; - tie(host, service) = GetHostService(checkable); + ScriptFrame frame (false); - ScriptFrame frame(true); - if (rule.GetScope()) - rule.GetScope()->CopyTo(frame.Locals); - frame.Locals->Set("host", host); - if (service) - frame.Locals->Set("service", service); + if (rule.GetScope() || rule.GetFTerm()) { + frame.Locals = new Dictionary(); - Value vinstances; + if (rule.GetScope()) { + rule.GetScope()->CopyTo(frame.Locals); + } + + checkable->GetFrozenLocalsForApply()->CopyTo(frame.Locals); + frame.Locals->Freeze(); + } else { + frame.Locals = checkable->GetFrozenLocalsForApply(); + } + + bool match = false; if (rule.GetFTerm()) { + Value vinstances; + try { vinstances = rule.GetFTerm()->Evaluate(frame); } catch (const std::exception&) { /* Silently ignore errors here and assume there are no instances. */ return false; } - } else { - vinstances = new Array({ "" }); - } - bool match = false; + if (vinstances.IsObjectType()) { + if (!rule.GetFVVar().IsEmpty()) + BOOST_THROW_EXCEPTION(ScriptError("Dictionary iterator requires value to be a dictionary.", di)); - if (vinstances.IsObjectType()) { - if (!rule.GetFVVar().IsEmpty()) - BOOST_THROW_EXCEPTION(ScriptError("Dictionary iterator requires value to be a dictionary.", di)); + Array::Ptr arr = vinstances; - Array::Ptr arr = vinstances; + ObjectLock olock(arr); + for (const Value& instance : arr) { + String name = rule.GetName(); - ObjectLock olock(arr); - for (const Value& instance : arr) { - String name = rule.GetName(); - - if (!rule.GetFKVar().IsEmpty()) { - frame.Locals->Set(rule.GetFKVar(), instance); + frame.Locals->Set(rule.GetFKVar(), instance, true); name += instance; + + if (EvaluateApplyRuleInstance(checkable, name, frame, rule, skipFilter)) + match = true; } + } else if (vinstances.IsObjectType()) { + if (rule.GetFVVar().IsEmpty()) + BOOST_THROW_EXCEPTION(ScriptError("Array iterator requires value to be an array.", di)); - if (EvaluateApplyRuleInstance(checkable, name, frame, rule, skipFilter)) - match = true; - } - } else if (vinstances.IsObjectType()) { - if (rule.GetFVVar().IsEmpty()) - BOOST_THROW_EXCEPTION(ScriptError("Array iterator requires value to be an array.", di)); - - Dictionary::Ptr dict = vinstances; - - for (const String& key : dict->GetKeys()) { - frame.Locals->Set(rule.GetFKVar(), key); - frame.Locals->Set(rule.GetFVVar(), dict->Get(key)); - - if (EvaluateApplyRuleInstance(checkable, rule.GetName() + key, frame, rule, skipFilter)) - match = true; + Dictionary::Ptr dict = vinstances; + ObjectLock olock (dict); + + for (auto& kv : dict) { + frame.Locals->Set(rule.GetFKVar(), kv.first, true); + frame.Locals->Set(rule.GetFVVar(), kv.second, true); + + if (EvaluateApplyRuleInstance(checkable, rule.GetName() + kv.first, frame, rule, skipFilter)) + match = true; + } } + } else if (EvaluateApplyRuleInstance(checkable, rule.GetName(), frame, rule, skipFilter)) { + match = true; } return match; diff --git a/lib/icinga/host.cpp b/lib/icinga/host.cpp index 1db9802c6..e096c42b8 100644 --- a/lib/icinga/host.cpp +++ b/lib/icinga/host.cpp @@ -308,3 +308,8 @@ bool Host::ResolveMacro(const String& macro, const CheckResult::Ptr&, Value *res return false; } + +Dictionary::Ptr Host::MakeLocalsForApply() +{ + return new Dictionary({{ "host", this }}); +} diff --git a/lib/icinga/host.hpp b/lib/icinga/host.hpp index 4538c21c4..7c3b1be9f 100644 --- a/lib/icinga/host.hpp +++ b/lib/icinga/host.hpp @@ -55,6 +55,8 @@ protected: void CreateChildObjects(const Type::Ptr& childType) override; + Dictionary::Ptr MakeLocalsForApply() override; + private: mutable std::mutex m_ServicesMutex; std::map > m_Services; diff --git a/lib/icinga/hostgroup.cpp b/lib/icinga/hostgroup.cpp index 5dbeaeafd..1b2bff4a9 100644 --- a/lib/icinga/hostgroup.cpp +++ b/lib/icinga/hostgroup.cpp @@ -25,10 +25,20 @@ bool HostGroup::EvaluateObjectRule(const Host::Ptr& host, const ConfigItem::Ptr& CONTEXT("Evaluating rule for group '" << groupName << "'"); - ScriptFrame frame(true); - if (group->GetScope()) - group->GetScope()->CopyTo(frame.Locals); - frame.Locals->Set("host", host); + ScriptFrame frame (false); + + if (group->GetScope()) { + frame.Locals = new Dictionary(); + + if (group->GetScope()) { + group->GetScope()->CopyTo(frame.Locals); + } + + host->GetFrozenLocalsForApply()->CopyTo(frame.Locals); + frame.Locals->Freeze(); + } else { + frame.Locals = host->GetFrozenLocalsForApply(); + } if (!group->GetFilter()->Evaluate(frame).GetValue().ToBool()) return false; diff --git a/lib/icinga/notification-apply.cpp b/lib/icinga/notification-apply.cpp index 1faf45a92..5cbeade8e 100644 --- a/lib/icinga/notification-apply.cpp +++ b/lib/icinga/notification-apply.cpp @@ -68,63 +68,66 @@ bool Notification::EvaluateApplyRule(const Checkable::Ptr& checkable, const Appl CONTEXT("Evaluating 'apply' rule (" << di << ")"); - Host::Ptr host; - Service::Ptr service; - tie(host, service) = GetHostService(checkable); + ScriptFrame frame (false); - ScriptFrame frame(true); - if (rule.GetScope()) - rule.GetScope()->CopyTo(frame.Locals); - frame.Locals->Set("host", host); - if (service) - frame.Locals->Set("service", service); + if (rule.GetScope() || rule.GetFTerm()) { + frame.Locals = new Dictionary(); - Value vinstances; + if (rule.GetScope()) { + rule.GetScope()->CopyTo(frame.Locals); + } + + checkable->GetFrozenLocalsForApply()->CopyTo(frame.Locals); + frame.Locals->Freeze(); + } else { + frame.Locals = checkable->GetFrozenLocalsForApply(); + } + + bool match = false; if (rule.GetFTerm()) { + Value vinstances; + try { vinstances = rule.GetFTerm()->Evaluate(frame); } catch (const std::exception&) { /* Silently ignore errors here and assume there are no instances. */ return false; } - } else { - vinstances = new Array({ "" }); - } - bool match = false; + if (vinstances.IsObjectType()) { + if (!rule.GetFVVar().IsEmpty()) + BOOST_THROW_EXCEPTION(ScriptError("Dictionary iterator requires value to be a dictionary.", di)); - if (vinstances.IsObjectType()) { - if (!rule.GetFVVar().IsEmpty()) - BOOST_THROW_EXCEPTION(ScriptError("Dictionary iterator requires value to be a dictionary.", di)); + Array::Ptr arr = vinstances; - Array::Ptr arr = vinstances; + ObjectLock olock(arr); + for (const Value& instance : arr) { + String name = rule.GetName(); - ObjectLock olock(arr); - for (const Value& instance : arr) { - String name = rule.GetName(); - - if (!rule.GetFKVar().IsEmpty()) { - frame.Locals->Set(rule.GetFKVar(), instance); + frame.Locals->Set(rule.GetFKVar(), instance, true); name += instance; + + if (EvaluateApplyRuleInstance(checkable, name, frame, rule, skipFilter)) + match = true; } + } else if (vinstances.IsObjectType()) { + if (rule.GetFVVar().IsEmpty()) + BOOST_THROW_EXCEPTION(ScriptError("Array iterator requires value to be an array.", di)); - if (EvaluateApplyRuleInstance(checkable, name, frame, rule, skipFilter)) - match = true; - } - } else if (vinstances.IsObjectType()) { - if (rule.GetFVVar().IsEmpty()) - BOOST_THROW_EXCEPTION(ScriptError("Array iterator requires value to be an array.", di)); - - Dictionary::Ptr dict = vinstances; - - for (const String& key : dict->GetKeys()) { - frame.Locals->Set(rule.GetFKVar(), key); - frame.Locals->Set(rule.GetFVVar(), dict->Get(key)); - - if (EvaluateApplyRuleInstance(checkable, rule.GetName() + key, frame, rule, skipFilter)) - match = true; + Dictionary::Ptr dict = vinstances; + ObjectLock olock (dict); + + for (auto& kv : dict) { + frame.Locals->Set(rule.GetFKVar(), kv.first, true); + frame.Locals->Set(rule.GetFVVar(), kv.second, true); + + if (EvaluateApplyRuleInstance(checkable, rule.GetName() + kv.first, frame, rule, skipFilter)) + match = true; + } } + } else if (EvaluateApplyRuleInstance(checkable, rule.GetName(), frame, rule, skipFilter)) { + match = true; } return match; diff --git a/lib/icinga/scheduleddowntime-apply.cpp b/lib/icinga/scheduleddowntime-apply.cpp index de875695e..5e4b4a892 100644 --- a/lib/icinga/scheduleddowntime-apply.cpp +++ b/lib/icinga/scheduleddowntime-apply.cpp @@ -67,63 +67,66 @@ bool ScheduledDowntime::EvaluateApplyRule(const Checkable::Ptr& checkable, const CONTEXT("Evaluating 'apply' rule (" << di << ")"); - Host::Ptr host; - Service::Ptr service; - tie(host, service) = GetHostService(checkable); + ScriptFrame frame (false); - ScriptFrame frame(true); - if (rule.GetScope()) - rule.GetScope()->CopyTo(frame.Locals); - frame.Locals->Set("host", host); - if (service) - frame.Locals->Set("service", service); + if (rule.GetScope() || rule.GetFTerm()) { + frame.Locals = new Dictionary(); - Value vinstances; + if (rule.GetScope()) { + rule.GetScope()->CopyTo(frame.Locals); + } + + checkable->GetFrozenLocalsForApply()->CopyTo(frame.Locals); + frame.Locals->Freeze(); + } else { + frame.Locals = checkable->GetFrozenLocalsForApply(); + } + + bool match = false; if (rule.GetFTerm()) { + Value vinstances; + try { vinstances = rule.GetFTerm()->Evaluate(frame); } catch (const std::exception&) { /* Silently ignore errors here and assume there are no instances. */ return false; } - } else { - vinstances = new Array({ "" }); - } - bool match = false; + if (vinstances.IsObjectType()) { + if (!rule.GetFVVar().IsEmpty()) + BOOST_THROW_EXCEPTION(ScriptError("Dictionary iterator requires value to be a dictionary.", di)); - if (vinstances.IsObjectType()) { - if (!rule.GetFVVar().IsEmpty()) - BOOST_THROW_EXCEPTION(ScriptError("Dictionary iterator requires value to be a dictionary.", di)); + Array::Ptr arr = vinstances; - Array::Ptr arr = vinstances; + ObjectLock olock(arr); + for (const Value& instance : arr) { + String name = rule.GetName(); - ObjectLock olock(arr); - for (const Value& instance : arr) { - String name = rule.GetName(); - - if (!rule.GetFKVar().IsEmpty()) { - frame.Locals->Set(rule.GetFKVar(), instance); + frame.Locals->Set(rule.GetFKVar(), instance, true); name += instance; + + if (EvaluateApplyRuleInstance(checkable, name, frame, rule, skipFilter)) + match = true; } + } else if (vinstances.IsObjectType()) { + if (rule.GetFVVar().IsEmpty()) + BOOST_THROW_EXCEPTION(ScriptError("Array iterator requires value to be an array.", di)); - if (EvaluateApplyRuleInstance(checkable, name, frame, rule, skipFilter)) - match = true; - } - } else if (vinstances.IsObjectType()) { - if (rule.GetFVVar().IsEmpty()) - BOOST_THROW_EXCEPTION(ScriptError("Array iterator requires value to be an array.", di)); - - Dictionary::Ptr dict = vinstances; - - for (const String& key : dict->GetKeys()) { - frame.Locals->Set(rule.GetFKVar(), key); - frame.Locals->Set(rule.GetFVVar(), dict->Get(key)); - - if (EvaluateApplyRuleInstance(checkable, rule.GetName() + key, frame, rule, skipFilter)) - match = true; + Dictionary::Ptr dict = vinstances; + ObjectLock olock (dict); + + for (auto& kv : dict) { + frame.Locals->Set(rule.GetFKVar(), kv.first, true); + frame.Locals->Set(rule.GetFVVar(), kv.second, true); + + if (EvaluateApplyRuleInstance(checkable, rule.GetName() + kv.first, frame, rule, skipFilter)) + match = true; + } } + } else if (EvaluateApplyRuleInstance(checkable, rule.GetName(), frame, rule, skipFilter)) { + match = true; } return match; diff --git a/lib/icinga/service-apply.cpp b/lib/icinga/service-apply.cpp index b32bd7b24..fd1418502 100644 --- a/lib/icinga/service-apply.cpp +++ b/lib/icinga/service-apply.cpp @@ -62,57 +62,68 @@ bool Service::EvaluateApplyRule(const Host::Ptr& host, const ApplyRule& rule, bo CONTEXT("Evaluating 'apply' rule (" << di << ")"); - ScriptFrame frame(true); - if (rule.GetScope()) - rule.GetScope()->CopyTo(frame.Locals); - frame.Locals->Set("host", host); + ScriptFrame frame (false); - Value vinstances; + if (rule.GetScope() || rule.GetFTerm()) { + frame.Locals = new Dictionary(); + + if (rule.GetScope()) { + rule.GetScope()->CopyTo(frame.Locals); + } + + host->GetFrozenLocalsForApply()->CopyTo(frame.Locals); + frame.Locals->Freeze(); + } else { + frame.Locals = host->GetFrozenLocalsForApply(); + } + + bool match = false; if (rule.GetFTerm()) { + Value vinstances; + try { vinstances = rule.GetFTerm()->Evaluate(frame); } catch (const std::exception&) { /* Silently ignore errors here and assume there are no instances. */ return false; } - } else { - vinstances = new Array({ "" }); - } - bool match = false; + if (vinstances.IsObjectType()) { + if (!rule.GetFVVar().IsEmpty()) + BOOST_THROW_EXCEPTION(ScriptError("Dictionary iterator requires value to be a dictionary.", di)); - if (vinstances.IsObjectType()) { - if (!rule.GetFVVar().IsEmpty()) - BOOST_THROW_EXCEPTION(ScriptError("Dictionary iterator requires value to be a dictionary.", di)); + Array::Ptr arr = vinstances; - Array::Ptr arr = vinstances; + ObjectLock olock(arr); + for (const Value& instance : arr) { + String name = rule.GetName(); - ObjectLock olock(arr); - for (const Value& instance : arr) { - String name = rule.GetName(); + if (!rule.GetFKVar().IsEmpty()) { + frame.Locals->Set(rule.GetFKVar(), instance, true); + name += instance; + } - if (!rule.GetFKVar().IsEmpty()) { - frame.Locals->Set(rule.GetFKVar(), instance); - name += instance; + if (EvaluateApplyRuleInstance(host, name, frame, rule, skipFilter)) + match = true; } + } else if (vinstances.IsObjectType()) { + if (rule.GetFVVar().IsEmpty()) + BOOST_THROW_EXCEPTION(ScriptError("Array iterator requires value to be an array.", di)); - if (EvaluateApplyRuleInstance(host, name, frame, rule, skipFilter)) - match = true; - } - } else if (vinstances.IsObjectType()) { - if (rule.GetFVVar().IsEmpty()) - BOOST_THROW_EXCEPTION(ScriptError("Array iterator requires value to be an array.", di)); - - Dictionary::Ptr dict = vinstances; - - for (const String& key : dict->GetKeys()) { - frame.Locals->Set(rule.GetFKVar(), key); - frame.Locals->Set(rule.GetFVVar(), dict->Get(key)); - - if (EvaluateApplyRuleInstance(host, rule.GetName() + key, frame, rule, skipFilter)) - match = true; + Dictionary::Ptr dict = vinstances; + ObjectLock olock (dict); + + for (auto& kv : dict) { + frame.Locals->Set(rule.GetFKVar(), kv.first, true); + frame.Locals->Set(rule.GetFVVar(), kv.second, true); + + if (EvaluateApplyRuleInstance(host, rule.GetName() + kv.first, frame, rule, skipFilter)) + match = true; + } } + } else if (EvaluateApplyRuleInstance(host, rule.GetName(), frame, rule, skipFilter)) { + match = true; } return match; diff --git a/lib/icinga/service.cpp b/lib/icinga/service.cpp index 4ac9ac6ea..d364fde6b 100644 --- a/lib/icinga/service.cpp +++ b/lib/icinga/service.cpp @@ -261,3 +261,11 @@ std::pair icinga::GetHostService(const Checkable::Ptr& else return std::make_pair(static_pointer_cast(checkable), nullptr); } + +Dictionary::Ptr Service::MakeLocalsForApply() +{ + return new Dictionary({ + { "host", m_Host }, + { "service", this } + }); +} diff --git a/lib/icinga/service.hpp b/lib/icinga/service.hpp index dfe3950d1..5530f8373 100644 --- a/lib/icinga/service.hpp +++ b/lib/icinga/service.hpp @@ -49,6 +49,8 @@ public: protected: void CreateChildObjects(const Type::Ptr& childType) override; + Dictionary::Ptr MakeLocalsForApply() override; + private: Host::Ptr m_Host; diff --git a/lib/icinga/servicegroup.cpp b/lib/icinga/servicegroup.cpp index 6e8d8f07f..9096b147b 100644 --- a/lib/icinga/servicegroup.cpp +++ b/lib/icinga/servicegroup.cpp @@ -25,13 +25,20 @@ bool ServiceGroup::EvaluateObjectRule(const Service::Ptr& service, const ConfigI CONTEXT("Evaluating rule for group '" << groupName << "'"); - Host::Ptr host = service->GetHost(); + ScriptFrame frame (false); - ScriptFrame frame(true); - if (group->GetScope()) - group->GetScope()->CopyTo(frame.Locals); - frame.Locals->Set("host", host); - frame.Locals->Set("service", service); + if (group->GetScope()) { + frame.Locals = new Dictionary(); + + if (group->GetScope()) { + group->GetScope()->CopyTo(frame.Locals); + } + + service->GetFrozenLocalsForApply()->CopyTo(frame.Locals); + frame.Locals->Freeze(); + } else { + frame.Locals = service->GetFrozenLocalsForApply(); + } if (!group->GetFilter()->Evaluate(frame).GetValue().ToBool()) return false;