From e047e06fc811c1468ba9d319239c45e58c6ae77e Mon Sep 17 00:00:00 2001 From: Gunnar Beutner Date: Wed, 6 Feb 2013 12:09:50 +0100 Subject: [PATCH] Finish implementing %validator. Fixes #3634 --- docs/icinga2-config.txt | 21 ++- itl/types.conf | 24 ++- lib/base/utility.cpp | 14 +- lib/config/configtype.cpp | 316 ++++++++++++++++++++------------------ lib/icinga/host.cpp | 34 +++- lib/icinga/host.h | 2 +- 6 files changed, 245 insertions(+), 166 deletions(-) diff --git a/docs/icinga2-config.txt b/docs/icinga2-config.txt index ce55e8f29..147f7b461 100644 --- a/docs/icinga2-config.txt +++ b/docs/icinga2-config.txt @@ -352,29 +352,34 @@ Example: ------------------------------------------------------------------------------- type Pizza { - number radius, + %require "radius", + %attribute number "radius", - dictionary ingredients { - string *, + %attribute dictionary "ingredients" { + %validator "native::ValidateIngredients", - dictionary * { - number quantity, - string name + %attribute string "*", + + %attribute dictionary "*" { + %attribute number "quantity", + %attribute string "name" } }, - any custom::* + %attribute any "custom::*" } ------------------------------------------------------------------------------- The Pizza definition provides the following validation rules: -* Pizza objects may contain an attribute "radius" which has to be a number. +* Pizza objects must contain an attribute "radius" which has to be a number. * Pizza objects may contain an attribute "ingredients" which has to be a dictionary. * Elements in the ingredients dictionary can be either a string or a dictionary. * If they're a dictionary they may contain attributes "quantity" (of type number) and "name" (of type string). +* The script function "native::ValidateIngredients" is run to perform further +validation of the ingredients dictionary. * Pizza objects may contain attribute matching the pattern "custom::*" of any type. diff --git a/itl/types.conf b/itl/types.conf index 8a41bf0a9..e83a925aa 100644 --- a/itl/types.conf +++ b/itl/types.conf @@ -58,7 +58,29 @@ type Host { %attribute string "*" }, %attribute dictionary "services" { - %attribute any "*" /* TODO: more specific validation rules */ + %validator "native::ValidateServiceDictionary", + + %attribute string "*", + %attribute dictionary "*" { + %attribute string "service", + + %attribute dictionary "macros" { + %attribute string "*" + }, + + %attribute number "check_interval", + %attribute number "retry_interval", + + %attribute dictionary "servicegroups" { + %attribute string "*" + }, + %attribute dictionary "checkers" { + %attribute string "*" + }, + %attribute dictionary "dependencies" { + %attribute string "*" + } + } }, /* service attributes */ diff --git a/lib/base/utility.cpp b/lib/base/utility.cpp index 42e194a7c..615ee3a82 100644 --- a/lib/base/utility.cpp +++ b/lib/base/utility.cpp @@ -507,7 +507,17 @@ bool Utility::Glob(const String& pathSpec, const function& */ void Utility::WaitUntil(const function& predicate) { - while (!predicate()) - Application::GetInstance()->ProcessEvents(); + while (!predicate()) { + Application::Ptr instance = Application::GetInstance(); + + /* Waiting for a predicate requires an application instance. + * This means we cannot do certain asynchronous things + * (like spawning a process) until the application instance + * has been initialized. */ + if (!instance) + throw_exception(runtime_error("Waiting for predicate failed: Application instance is not initialized.")); + + instance->ProcessEvents(); + } } diff --git a/lib/config/configtype.cpp b/lib/config/configtype.cpp index 22c3725de..f510630c2 100644 --- a/lib/config/configtype.cpp +++ b/lib/config/configtype.cpp @@ -1,148 +1,168 @@ -/****************************************************************************** - * Icinga 2 * - * Copyright (C) 2012 Icinga Development Team (http://www.icinga.org/) * - * * - * This program is free software; you can redistribute it and/or * - * modify it under the terms of the GNU General Public License * - * as published by the Free Software Foundation; either version 2 * - * of the License, or (at your option) any later version. * - * * - * This program is distributed in the hope that it will be useful, * - * but WITHOUT ANY WARRANTY; without even the implied warranty of * - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * - * GNU General Public License for more details. * - * * - * You should have received a copy of the GNU General Public License * - * along with this program; if not, write to the Free Software Foundation * - * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * - ******************************************************************************/ - -#include "i2-config.h" - -using namespace icinga; - -ConfigType::ConfigType(const String& name, const DebugInfo& debuginfo) - : m_Name(name), m_RuleList(boost::make_shared()), m_DebugInfo(debuginfo) -{ } - -String ConfigType::GetName(void) const -{ - return m_Name; -} - -String ConfigType::GetParent(void) const -{ - return m_Parent; -} - -void ConfigType::SetParent(const String& parent) -{ - m_Parent = parent; -} - -TypeRuleList::Ptr ConfigType::GetRuleList(void) const -{ - return m_RuleList; -} - -DebugInfo ConfigType::GetDebugInfo(void) const -{ - return m_DebugInfo; -} - -void ConfigType::ValidateItem(const ConfigItem::Ptr& item) const -{ - Dictionary::Ptr attrs = item->Link(); - - /* Don't validate abstract items. */ - if (attrs->Get("__abstract")) - return; - - vector locations; - locations.push_back("Object '" + item->GetName() + "' (Type: '" + item->GetType() + "')"); - - ConfigType::Ptr parent; - if (m_Parent.IsEmpty()) { - if (GetName() != "DynamicObject") - parent = ConfigCompilerContext::GetContext()->GetType("DynamicObject"); - } else { - parent = ConfigCompilerContext::GetContext()->GetType(m_Parent); - } - - vector ruleLists; - if (parent) - ruleLists.push_back(parent->m_RuleList); - - ruleLists.push_back(m_RuleList); - - ValidateDictionary(attrs, ruleLists, locations); -} - -String ConfigType::LocationToString(const vector& locations) -{ - bool first = true; - String stack; - BOOST_FOREACH(const String& location, locations) { - if (!first) - stack += " -> "; - else - first = false; - - stack += location; - } - - return stack; -} - -void ConfigType::ValidateDictionary(const Dictionary::Ptr& dictionary, - const vector& ruleLists, vector& locations) -{ - BOOST_FOREACH(const TypeRuleList::Ptr& ruleList, ruleLists) { - BOOST_FOREACH(const String& require, ruleList->GetRequires()) { - locations.push_back("Attribute '" + require + "'"); - - Value value = dictionary->Get(require); - - if (value.IsEmpty()) - ConfigCompilerContext::GetContext()->AddError(false, "Required attribute is missing: " + LocationToString(locations)); - - locations.pop_back(); - } - } - - String key; - Value value; - BOOST_FOREACH(tie(key, value), dictionary) { - TypeValidationResult overallResult = ValidationUnknownField; - vector subRuleLists; - - locations.push_back("Attribute '" + key + "'"); - - BOOST_FOREACH(const TypeRuleList::Ptr& ruleList, ruleLists) { - TypeRuleList::Ptr subRuleList; - TypeValidationResult result = ruleList->ValidateAttribute(key, value, &subRuleList); - - if (subRuleList) - subRuleLists.push_back(subRuleList); - - if (result == ValidationOK) { - overallResult = result; - break; - } - - if (result == ValidationInvalidType) - overallResult = result; - } - - if (overallResult == ValidationUnknownField) - ConfigCompilerContext::GetContext()->AddError(true, "Unknown attribute: " + LocationToString(locations)); - else if (overallResult == ValidationInvalidType) - ConfigCompilerContext::GetContext()->AddError(false, "Invalid type for attribute: " + LocationToString(locations)); - - if (subRuleLists.size() > 0 && value.IsObjectType()) - ValidateDictionary(value, subRuleLists, locations); - - locations.pop_back(); - } -} - +/****************************************************************************** + * Icinga 2 * + * Copyright (C) 2012 Icinga Development Team (http://www.icinga.org/) * + * * + * This program is free software; you can redistribute it and/or * + * modify it under the terms of the GNU General Public License * + * as published by the Free Software Foundation; either version 2 * + * of the License, or (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the Free Software Foundation * + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. * + ******************************************************************************/ + +#include "i2-config.h" + +using namespace icinga; + +ConfigType::ConfigType(const String& name, const DebugInfo& debuginfo) + : m_Name(name), m_RuleList(boost::make_shared()), m_DebugInfo(debuginfo) +{ } + +String ConfigType::GetName(void) const +{ + return m_Name; +} + +String ConfigType::GetParent(void) const +{ + return m_Parent; +} + +void ConfigType::SetParent(const String& parent) +{ + m_Parent = parent; +} + +TypeRuleList::Ptr ConfigType::GetRuleList(void) const +{ + return m_RuleList; +} + +DebugInfo ConfigType::GetDebugInfo(void) const +{ + return m_DebugInfo; +} + +void ConfigType::ValidateItem(const ConfigItem::Ptr& item) const +{ + Dictionary::Ptr attrs = item->Link(); + + /* Don't validate abstract items. */ + if (attrs->Get("__abstract")) + return; + + vector locations; + locations.push_back("Object '" + item->GetName() + "' (Type: '" + item->GetType() + "')"); + + ConfigType::Ptr parent; + if (m_Parent.IsEmpty()) { + if (GetName() != "DynamicObject") + parent = ConfigCompilerContext::GetContext()->GetType("DynamicObject"); + } else { + parent = ConfigCompilerContext::GetContext()->GetType(m_Parent); + } + + vector ruleLists; + if (parent) + ruleLists.push_back(parent->m_RuleList); + + ruleLists.push_back(m_RuleList); + + ValidateDictionary(attrs, ruleLists, locations); +} + +String ConfigType::LocationToString(const vector& locations) +{ + bool first = true; + String stack; + BOOST_FOREACH(const String& location, locations) { + if (!first) + stack += " -> "; + else + first = false; + + stack += location; + } + + return stack; +} + +void ConfigType::ValidateDictionary(const Dictionary::Ptr& dictionary, + const vector& ruleLists, vector& locations) +{ + BOOST_FOREACH(const TypeRuleList::Ptr& ruleList, ruleLists) { + BOOST_FOREACH(const String& require, ruleList->GetRequires()) { + locations.push_back("Attribute '" + require + "'"); + + Value value = dictionary->Get(require); + + if (value.IsEmpty()) { + ConfigCompilerContext::GetContext()->AddError(false, + "Required attribute is missing: " + LocationToString(locations)); + } + + locations.pop_back(); + } + + String validator = ruleList->GetValidator(); + + if (!validator.IsEmpty()) { + ScriptFunction::Ptr func = ScriptFunction::GetByName(validator); + + if (!func) + throw_exception(invalid_argument("Validator function '" + validator + "' does not exist.")); + + vector arguments; + arguments.push_back(LocationToString(locations)); + arguments.push_back(dictionary); + + ScriptTask::Ptr task = boost::make_shared(func, arguments); + task->Start(); + task->Wait(); + task->GetResult(); + } + } + + String key; + Value value; + BOOST_FOREACH(tie(key, value), dictionary) { + TypeValidationResult overallResult = ValidationUnknownField; + vector subRuleLists; + + locations.push_back("Attribute '" + key + "'"); + + BOOST_FOREACH(const TypeRuleList::Ptr& ruleList, ruleLists) { + TypeRuleList::Ptr subRuleList; + TypeValidationResult result = ruleList->ValidateAttribute(key, value, &subRuleList); + + if (subRuleList) + subRuleLists.push_back(subRuleList); + + if (result == ValidationOK) { + overallResult = result; + break; + } + + if (result == ValidationInvalidType) + overallResult = result; + } + + if (overallResult == ValidationUnknownField) + ConfigCompilerContext::GetContext()->AddError(true, "Unknown attribute: " + LocationToString(locations)); + else if (overallResult == ValidationInvalidType) + ConfigCompilerContext::GetContext()->AddError(false, "Invalid type for attribute: " + LocationToString(locations)); + + if (subRuleLists.size() > 0 && value.IsObjectType()) + ValidateDictionary(value, subRuleLists, locations); + + locations.pop_back(); + } +} + diff --git a/lib/icinga/host.cpp b/lib/icinga/host.cpp index c338c3b55..c0604d399 100644 --- a/lib/icinga/host.cpp +++ b/lib/icinga/host.cpp @@ -24,7 +24,7 @@ using namespace icinga; map > Host::m_ServicesCache; bool Host::m_ServicesCacheValid = true; -REGISTER_SCRIPTFUNCTION("native::ValidateHostItem", &Host::ValidateHostItem); +REGISTER_SCRIPTFUNCTION("native::ValidateServiceDictionary", &Host::ValidateServiceDictionary); static AttributeDescription hostAttributes[] = { { "acknowledgement", Attribute_Replicated }, @@ -407,18 +407,40 @@ void Host::ValidateServicesCache(void) m_ServicesCacheValid = true; } -void Host::ValidateHostItem(const ScriptTask::Ptr& task, const vector& arguments) +void Host::ValidateServiceDictionary(const ScriptTask::Ptr& task, const vector& arguments) { if (arguments.size() < 1) - throw_exception(invalid_argument("Missing argument: Host config item must be specified.")); + throw_exception(invalid_argument("Missing argument: Location must be specified.")); if (arguments.size() < 2) throw_exception(invalid_argument("Missing argument: Attribute dictionary must be specified.")); - ConfigItem::Ptr item = arguments[0]; + String location = arguments[0]; Dictionary::Ptr attrs = arguments[1]; - // TODO: validate item + String key; + Value value; + BOOST_FOREACH(tie(key, value), attrs) { + String name; - ConfigCompilerContext::GetContext()->AddError(false, "Hello World!"); + if (value.IsScalar()) { + name = value; + } else if (value.IsObjectType()) { + Dictionary::Ptr serviceDesc = value; + + if (serviceDesc->Contains("service")) + name = serviceDesc->Get("service"); + else + name = key; + } else { + continue; + } + + if (!ConfigItem::GetObject("Service", name)) { + ConfigCompilerContext::GetContext()->AddError(false, "Validation failed for " + + location + ": Service '" + name + "' not found."); + } + } + + task->FinishResult(Empty); } diff --git a/lib/icinga/host.h b/lib/icinga/host.h index 3ee32cd43..81f8e2e9d 100644 --- a/lib/icinga/host.h +++ b/lib/icinga/host.h @@ -63,7 +63,7 @@ public: set > GetServices(void) const; static void InvalidateServicesCache(void); - static void ValidateHostItem(const ScriptTask::Ptr& task, + static void ValidateServiceDictionary(const ScriptTask::Ptr& task, const std::vector& arguments); protected: