Improve config validation.

Fixes #4391
This commit is contained in:
Gunnar Beutner 2013-09-24 13:13:14 +02:00
parent 8568b4b701
commit a7e25beb06
19 changed files with 196 additions and 157 deletions

View File

@ -577,7 +577,7 @@ void CompatLog::ValidateRotationMethod(const String& location, const Dictionary:
if (!rotation_method.IsEmpty() && rotation_method != "HOURLY" && rotation_method != "DAILY" &&
rotation_method != "WEEKLY" && rotation_method != "MONTHLY" && rotation_method != "NONE") {
ConfigCompilerContext::GetInstance()->AddError(false, "Validation failed for " +
ConfigCompilerContext::GetInstance()->AddMessage(true, "Validation failed for " +
location + ": Rotation method '" + rotation_method + "' is invalid.");
}
}

View File

@ -58,50 +58,23 @@ static bool LoadConfigFiles(bool validateOnly)
ConfigCompiler::CompileText(name, fragment);
}
bool hasError = false;
BOOST_FOREACH(const ConfigCompilerError& error, ConfigCompilerContext::GetInstance()->GetErrors()) {
if (!error.Warning) {
hasError = true;
break;
}
}
/* Don't link or validate if we have already encountered at least one error. */
if (!hasError) {
ConfigItem::LinkItems();
ConfigItem::ValidateItems();
}
hasError = false;
BOOST_FOREACH(const ConfigCompilerError& error, ConfigCompilerContext::GetInstance()->GetErrors()) {
if (error.Warning) {
Log(LogWarning, "icinga-app", "Config warning: " + error.Message);
} else {
hasError = true;
Log(LogCritical, "icinga-app", "Config error: " + error.Message);
}
}
if (hasError)
return false;
if (validateOnly)
return true;
if (Application::GetInstance()) {
Log(LogCritical, "icinga-app", "You must not manually create an Application object.");
return false;
}
ConfigItemBuilder::Ptr builder = boost::make_shared<ConfigItemBuilder>();
builder->SetType(Application::GetApplicationType());
builder->SetName("application");
ConfigItem::Ptr item = builder->Compile();
item->Register();
ConfigItem::ActivateItems();
bool result = ConfigItem::ActivateItems(validateOnly);
BOOST_FOREACH(const ConfigCompilerMessage& message, ConfigCompilerContext::GetInstance()->GetMessages()) {
if (message.Error)
Log(LogCritical, "config", "Config error: " + message.Text);
else
Log(LogWarning, "config", "Config warning: " + message.Text);
}
if (!result)
return false;
ConfigItem::DiscardItems();
ConfigType::DiscardTypes();

View File

@ -1061,7 +1061,7 @@ YY_RULE_SETUP
{
std::ostringstream msgbuf;
msgbuf << "Unterminated string found: " << *yylloc;
ConfigCompilerContext::GetInstance()->AddError(false, msgbuf.str());
ConfigCompilerContext::GetInstance()->AddMessage(true, msgbuf.str());
BEGIN(INITIAL);
}
YY_BREAK
@ -1078,7 +1078,7 @@ YY_RULE_SETUP
/* error, constant is out-of-bounds */
std::ostringstream msgbuf;
msgbuf << "Constant is out-of-bounds: " << yytext << " " << *yylloc;
ConfigCompilerContext::GetInstance()->AddError(false, msgbuf.str());
ConfigCompilerContext::GetInstance()->AddMessage(true, msgbuf.str());
}
lb_append_char(&string_buf, result);
@ -1093,7 +1093,7 @@ YY_RULE_SETUP
*/
std::ostringstream msgbuf;
msgbuf << "Bad escape sequence found: " << yytext << " " << *yylloc;
ConfigCompilerContext::GetInstance()->AddError(false, msgbuf.str());
ConfigCompilerContext::GetInstance()->AddMessage(true, msgbuf.str());
}
YY_BREAK
case 6:

View File

@ -116,7 +116,7 @@ static char *lb_steal(lex_buf *lb)
<STRING>\n {
std::ostringstream msgbuf;
msgbuf << "Unterminated string found: " << *yylloc;
ConfigCompilerContext::GetInstance()->AddError(false, msgbuf.str());
ConfigCompilerContext::GetInstance()->AddMessage(true, msgbuf.str());
BEGIN(INITIAL);
}
@ -130,7 +130,7 @@ static char *lb_steal(lex_buf *lb)
/* error, constant is out-of-bounds */
std::ostringstream msgbuf;
msgbuf << "Constant is out-of-bounds: " << yytext << " " << *yylloc;
ConfigCompilerContext::GetInstance()->AddError(false, msgbuf.str());
ConfigCompilerContext::GetInstance()->AddMessage(true, msgbuf.str());
}
lb_append_char(&string_buf, result);
@ -142,7 +142,7 @@ static char *lb_steal(lex_buf *lb)
*/
std::ostringstream msgbuf;
msgbuf << "Bad escape sequence found: " << yytext << " " << *yylloc;
ConfigCompilerContext::GetInstance()->AddError(false, msgbuf.str());
ConfigCompilerContext::GetInstance()->AddMessage(true, msgbuf.str());
}
<STRING>\\n { lb_append_char(&string_buf, '\n'); }

View File

@ -267,7 +267,7 @@ void yyerror(YYLTYPE *locp, ConfigCompiler *, const char *err)
{
std::ostringstream message;
message << *locp << ": " << err;
ConfigCompilerContext::GetInstance()->AddError(false, message.str());
ConfigCompilerContext::GetInstance()->AddMessage(true, message.str());
}
int yyparse(ConfigCompiler *context);
@ -283,7 +283,7 @@ void ConfigCompiler::Compile(void)
try {
yyparse(this);
} catch (const std::exception& ex) {
ConfigCompilerContext::GetInstance()->AddError(false, boost::diagnostic_information(ex));
ConfigCompilerContext::GetInstance()->AddMessage(true, boost::diagnostic_information(ex));
}
}

View File

@ -124,7 +124,7 @@ void yyerror(YYLTYPE *locp, ConfigCompiler *, const char *err)
{
std::ostringstream message;
message << *locp << ": " << err;
ConfigCompilerContext::GetInstance()->AddError(false, message.str());
ConfigCompilerContext::GetInstance()->AddMessage(true, message.str());
}
int yyparse(ConfigCompiler *context);
@ -140,7 +140,7 @@ void ConfigCompiler::Compile(void)
try {
yyparse(this);
} catch (const std::exception& ex) {
ConfigCompilerContext::GetInstance()->AddError(false, boost::diagnostic_information(ex));
ConfigCompilerContext::GetInstance()->AddMessage(true, boost::diagnostic_information(ex));
}
}

View File

@ -25,19 +25,29 @@
using namespace icinga;
void ConfigCompilerContext::AddError(bool warning, const String& message)
void ConfigCompilerContext::AddMessage(bool error, const String& message)
{
m_Errors.push_back(ConfigCompilerError(warning, message));
m_Messages.push_back(ConfigCompilerMessage(error, message));
}
std::vector<ConfigCompilerError> ConfigCompilerContext::GetErrors(void) const
std::vector<ConfigCompilerMessage> ConfigCompilerContext::GetMessages(void) const
{
return m_Errors;
return m_Messages;
}
bool ConfigCompilerContext::HasErrors(void) const
{
BOOST_FOREACH(const ConfigCompilerMessage& message, m_Messages) {
if (message.Error)
return true;
}
return false;
}
void ConfigCompilerContext::Reset(void)
{
m_Errors.clear();
m_Messages.clear();
}
ConfigCompilerContext *ConfigCompilerContext::GetInstance(void)

View File

@ -27,13 +27,13 @@
namespace icinga
{
struct I2_CONFIG_API ConfigCompilerError
struct I2_CONFIG_API ConfigCompilerMessage
{
bool Warning;
String Message;
bool Error;
String Text;
ConfigCompilerError(bool warning, const String& message)
: Warning(warning), Message(message)
ConfigCompilerMessage(bool error, const String& text)
: Error(error), Text(text)
{ }
};
@ -43,15 +43,16 @@ struct I2_CONFIG_API ConfigCompilerError
class I2_CONFIG_API ConfigCompilerContext
{
public:
void AddError(bool warning, const String& message);
std::vector<ConfigCompilerError> GetErrors(void) const;
void AddMessage(bool error, const String& message);
std::vector<ConfigCompilerMessage> GetMessages(void) const;
bool HasErrors(void) const;
void Reset(void);
static ConfigCompilerContext *GetInstance(void);
private:
std::vector<ConfigCompilerError> m_Errors;
std::vector<ConfigCompilerMessage> m_Messages;
};
}

View File

@ -116,13 +116,13 @@ void ConfigItem::Link(void)
std::ostringstream message;
message << "Parent object '" << name << "' does not"
" exist (" << m_DebugInfo << ")";
BOOST_THROW_EXCEPTION(std::invalid_argument(message.str()));
ConfigCompilerContext::GetInstance()->AddMessage(true, message.str());
} else {
parent->Link();
ExpressionList::Ptr pexprl = parent->GetLinkedExpressionList();
m_LinkedExpressionList->AddExpression(Expression("", OperatorExecute, pexprl, m_DebugInfo));
}
parent->Link();
ExpressionList::Ptr pexprl = parent->GetLinkedExpressionList();
m_LinkedExpressionList->AddExpression(Expression("", OperatorExecute, pexprl, m_DebugInfo));
}
m_LinkedExpressionList->AddExpression(Expression("", OperatorExecute, m_ExpressionList, m_DebugInfo));
@ -233,41 +233,38 @@ ConfigItem::Ptr ConfigItem::GetObject(const String& type, const String& name)
return ConfigItem::Ptr();
}
void ConfigItem::LinkItems(void)
void ConfigItem::ValidateItem(void)
{
ConfigType::Ptr ctype = ConfigType::GetByName(GetType());
if (!ctype) {
ConfigCompilerContext::GetInstance()->AddMessage(false, "No validation type found for object '" + GetName() + "' of type '" + GetType() + "'");
return;
}
ctype->ValidateItem(GetSelf());
}
bool ConfigItem::ActivateItems(bool validateOnly)
{
if (ConfigCompilerContext::GetInstance()->HasErrors())
return false;
Log(LogInformation, "config", "Linking config items...");
ConfigItem::Ptr item;
BOOST_FOREACH(boost::tie(boost::tuples::ignore, item), m_Items) {
item->Link();
}
}
void ConfigItem::ValidateItems(void)
{
Log(LogInformation, "config", "Validating config items...");
if (ConfigCompilerContext::GetInstance()->HasErrors())
return false;
ConfigItem::Ptr item;
BOOST_FOREACH(boost::tie(boost::tuples::ignore, item), m_Items) {
ConfigType::Ptr ctype = ConfigType::GetByName(item->GetType());
if (!ctype) {
ConfigCompilerContext::GetInstance()->AddError(true, "No validation type found for object '" + item->GetName() + "' of type '" + item->GetType() + "'");
continue;
}
ctype->ValidateItem(item);
}
}
void ConfigItem::ActivateItems(void)
{
Log(LogInformation, "config", "Activating config items");
std::vector<DynamicObject::Ptr> objects;
ConfigItem::Ptr item;
BOOST_FOREACH(boost::tie(boost::tuples::ignore, item), m_Items) {
DynamicObject::Ptr object = item->Commit();
@ -279,6 +276,16 @@ void ConfigItem::ActivateItems(void)
object->OnConfigLoaded();
}
BOOST_FOREACH(boost::tie(boost::tuples::ignore, item), m_Items) {
item->ValidateItem();
}
if (ConfigCompilerContext::GetInstance()->HasErrors())
return false;
if (validateOnly)
return true;
/* restore the previous program state */
DynamicObject::RestoreObjects(Application::GetStatePath());
@ -293,6 +300,8 @@ void ConfigItem::ActivateItems(void)
ASSERT(object->IsActive());
}
}
return true;
}
void ConfigItem::DiscardItems(void)

View File

@ -62,9 +62,9 @@ public:
static ConfigItem::Ptr GetObject(const String& type,
const String& name);
static void LinkItems(void);
static void ValidateItems(void);
static void ActivateItems(void);
void ValidateItem(void);
static bool ActivateItems(bool validateOnly);
static void DiscardItems(void);
private:

View File

@ -121,7 +121,7 @@ void ConfigType::ValidateDictionary(const Dictionary::Ptr& dictionary,
Value value = dictionary->Get(require);
if (value.IsEmpty()) {
ConfigCompilerContext::GetInstance()->AddError(false,
ConfigCompilerContext::GetInstance()->AddMessage(true,
"Required attribute is missing: " + LocationToString(locations));
}
@ -175,14 +175,14 @@ void ConfigType::ValidateDictionary(const Dictionary::Ptr& dictionary,
}
if (overallResult == ValidationUnknownField)
ConfigCompilerContext::GetInstance()->AddError(true, "Unknown attribute: " + LocationToString(locations));
ConfigCompilerContext::GetInstance()->AddMessage(false, "Unknown attribute: " + LocationToString(locations));
else if (overallResult == ValidationInvalidType) {
String message = "Invalid value for attribute: " + LocationToString(locations);
if (!hint.IsEmpty())
message += ": " + hint;
ConfigCompilerContext::GetInstance()->AddError(false, message);
ConfigCompilerContext::GetInstance()->AddMessage(true, message);
}
if (!subRuleLists.empty() && value.IsObjectType<Dictionary>())
@ -204,7 +204,7 @@ void ConfigType::ValidateArray(const Array::Ptr& array,
locations.push_back("Attribute '" + require + "'");
if (array->GetLength() < index) {
ConfigCompilerContext::GetInstance()->AddError(false,
ConfigCompilerContext::GetInstance()->AddMessage(true,
"Required array index is missing: " + LocationToString(locations));
}
@ -261,14 +261,14 @@ void ConfigType::ValidateArray(const Array::Ptr& array,
}
if (overallResult == ValidationUnknownField)
ConfigCompilerContext::GetInstance()->AddError(true, "Unknown attribute: " + LocationToString(locations));
ConfigCompilerContext::GetInstance()->AddMessage(false, "Unknown attribute: " + LocationToString(locations));
else if (overallResult == ValidationInvalidType) {
String message = "Invalid value for array index: " + LocationToString(locations);
if (!hint.IsEmpty())
message += ": " + hint;
ConfigCompilerContext::GetInstance()->AddError(false, message);
ConfigCompilerContext::GetInstance()->AddMessage(true, message);
}
if (!subRuleLists.empty() && value.IsObjectType<Dictionary>())

View File

@ -58,6 +58,11 @@ void Expression::Execute(const Dictionary::Ptr& dictionary) const
Array::Ptr array;
switch (m_Operator) {
case OperatorNop:
/* Nothing to do here. */
return;
case OperatorExecute:
if (!valueExprl)
BOOST_THROW_EXCEPTION(std::invalid_argument("Operand for OperatorExecute must be an ExpressionList."));
@ -165,3 +170,41 @@ void Expression::ExtractFiltered(const std::set<String, string_iless>& keys, con
exprl->ExtractFiltered(keys, result);
}
}
void Expression::ErasePath(const std::vector<String>& path)
{
ASSERT(!path.empty());
if (path[0] == m_Key) {
if (path.size() == 1) {
m_Operator = OperatorNop;
} else if (m_Value.IsObjectType<ExpressionList>()) {
ExpressionList::Ptr exprl = m_Value;
std::vector<String> sub_path(path.begin() + 1, path.end());
exprl->ErasePath(sub_path);
}
} else if (m_Operator == OperatorExecute) {
ExpressionList::Ptr exprl = m_Value;
exprl->ErasePath(path);
}
}
void Expression::FindDebugInfoPath(const std::vector<String>& path, DebugInfo& result) const
{
ASSERT(!path.empty());
if (path[0] == m_Key) {
if (path.size() == 1) {
result = m_DebugInfo;
} else if (m_Value.IsObjectType<ExpressionList>()) {
ExpressionList::Ptr exprl = m_Value;
std::vector<String> sub_path(path.begin() + 1, path.end());
exprl->FindDebugInfoPath(sub_path, result);
}
} else if (m_Operator == OperatorExecute) {
ExpressionList::Ptr exprl = m_Value;
exprl->FindDebugInfoPath(path, result);
}
}

View File

@ -37,6 +37,7 @@ namespace icinga
*/
enum ExpressionOperator
{
OperatorNop,
OperatorExecute,
OperatorSet,
OperatorPlus,
@ -63,6 +64,10 @@ public:
void ExtractPath(const std::vector<String>& path, const shared_ptr<ExpressionList>& result) const;
void ExtractFiltered(const std::set<String, string_iless>& keys, const shared_ptr<ExpressionList>& result) const;
void ErasePath(const std::vector<String>& path);
void FindDebugInfoPath(const std::vector<String>& path, DebugInfo& result) const;
private:
String m_Key;
ExpressionOperator m_Operator;

View File

@ -68,3 +68,17 @@ void ExpressionList::ExtractFiltered(const std::set<String, string_iless>& keys,
expression.ExtractFiltered(keys, result);
}
}
void ExpressionList::ErasePath(const std::vector<String>& path)
{
BOOST_FOREACH(Expression& expression, m_Expressions) {
expression.ErasePath(path);
}
}
void ExpressionList::FindDebugInfoPath(const std::vector<String>& path, DebugInfo& result) const
{
BOOST_FOREACH(const Expression& expression, m_Expressions) {
expression.FindDebugInfoPath(path, result);
}
}

View File

@ -47,6 +47,10 @@ public:
void ExtractPath(const std::vector<String>& path, const ExpressionList::Ptr& result) const;
void ExtractFiltered(const std::set<String, string_iless>& keys, const ExpressionList::Ptr& result) const;
void ErasePath(const std::vector<String>& path);
void FindDebugInfoPath(const std::vector<String>& path, DebugInfo& result) const;
private:
std::vector<Expression> m_Expressions;
};

View File

@ -36,8 +36,6 @@
using namespace icinga;
REGISTER_SCRIPTFUNCTION(ValidateServiceDictionary, &Host::ValidateServiceDictionary);
REGISTER_TYPE(Host);
void Host::Start(void)
@ -197,7 +195,17 @@ void Host::UpdateSlaveServices(void)
namebuf << GetName() << ":" << svcname;
String name = namebuf.str();
ConfigItemBuilder::Ptr builder = boost::make_shared<ConfigItemBuilder>(item->GetDebugInfo());
std::vector<String> path;
path.push_back("services");
path.push_back(svcname);
DebugInfo di;
item->GetLinkedExpressionList()->FindDebugInfoPath(path, di);
if (di.Path.IsEmpty())
di = item->GetDebugInfo();
ConfigItemBuilder::Ptr builder = boost::make_shared<ConfigItemBuilder>(di);
builder->SetType("Service");
builder->SetName(name);
builder->AddExpression("host_name", OperatorSet, GetName());
@ -238,12 +246,13 @@ void Host::UpdateSlaveServices(void)
builder->AddExpressionList(host_exprl);
/* Clone attributes from the service expression list. */
std::vector<String> path;
path.push_back("services");
path.push_back(svcname);
ExpressionList::Ptr svc_exprl = boost::make_shared<ExpressionList>();
item->GetLinkedExpressionList()->ExtractPath(path, svc_exprl);
std::vector<String> dpath;
dpath.push_back("templates");
svc_exprl->ErasePath(dpath);
builder->AddExpressionList(svc_exprl);
ConfigItem::Ptr serviceItem = builder->Compile();
@ -290,42 +299,6 @@ int Host::GetTotalServices(void) const
return GetServices().size();
}
Value Host::ValidateServiceDictionary(const String& location, const Dictionary::Ptr& attrs)
{
ObjectLock olock(attrs);
String key;
Value value;
BOOST_FOREACH(boost::tie(key, value), attrs) {
std::vector<String> templates;
if (!value.IsObjectType<Dictionary>())
BOOST_THROW_EXCEPTION(std::invalid_argument("Service description must be a dictionary."));
Dictionary::Ptr serviceDesc = value;
Array::Ptr templatesArray = serviceDesc->Get("templates");
if (templatesArray) {
ObjectLock tlock(templatesArray);
BOOST_FOREACH(const Value& tmpl, templatesArray) {
templates.push_back(tmpl);
}
}
BOOST_FOREACH(const String& name, templates) {
ConfigItem::Ptr item = ConfigItem::GetObject("Service", name);
if (!item)
ConfigCompilerContext::GetInstance()->AddError(false, "Validation failed for " +
location + ": Template '" + name + "' not found.");
}
}
return Empty;
}
Service::Ptr Host::GetServiceByShortName(const Value& name) const
{
if (name.IsScalar()) {

View File

@ -104,8 +104,6 @@ public:
int GetTotalServices(void) const;
static Value ValidateServiceDictionary(const String& location, const Dictionary::Ptr& attrs);
static HostState CalculateState(ServiceState state, bool reachable);
HostState GetState(void) const;

View File

@ -36,14 +36,12 @@ type Host {
}
},
%attribute dictionary "services" {
%validator "ValidateServiceDictionary",
%attribute dictionary "*" {
%attribute array "templates" {
%attribute name(Service) "*"
},
%attribute string "short_name",
/* %attribute string "short_name",
%attribute string "display_name",
%attribute dictionary "macros" {
@ -112,11 +110,11 @@ type Host {
%attribute number "notification_state_filter",
%attribute name(TimePeriod) "notification_period"
}
},
},*/
}
},
%attribute dictionary "notifications" {
/* %attribute dictionary "notifications" {
%attribute dictionary "*" {
%attribute array "templates" {
%attribute name(Notification) "*"
@ -142,7 +140,7 @@ type Host {
%attribute number "notification_state_filter",
%attribute name(TimePeriod) "notification_period"
}
},
},*/
/* service attributes */
%attribute number "max_check_attempts",
@ -244,7 +242,7 @@ type Service {
%attribute name(Notification) "*"
},
%attribute dictionary "macros" {
/* %attribute dictionary "macros" {
%attribute string "*"
},
@ -262,7 +260,7 @@ type Service {
%attribute number "notification_type_filter",
%attribute number "notification_state_filter",
%attribute name(TimePeriod) "notification_period"
%attribute name(TimePeriod) "notification_period"*/
}
}
}

View File

@ -147,7 +147,17 @@ void Service::UpdateSlaveNotifications(void)
namebuf << GetName() << ":" << nfcname;
String name = namebuf.str();
ConfigItemBuilder::Ptr builder = boost::make_shared<ConfigItemBuilder>(item->GetDebugInfo());
std::vector<String> path;
path.push_back("notifications");
path.push_back(nfcname);
DebugInfo di;
item->GetLinkedExpressionList()->FindDebugInfoPath(path, di);
if (di.Path.IsEmpty())
di = item->GetDebugInfo();
ConfigItemBuilder::Ptr builder = boost::make_shared<ConfigItemBuilder>(di);
builder->SetType("Notification");
builder->SetName(name);
builder->AddExpression("host_name", OperatorSet, host->GetName());
@ -183,12 +193,13 @@ void Service::UpdateSlaveNotifications(void)
builder->AddExpressionList(svc_exprl);
/* Clone attributes from the notification expression list. */
std::vector<String> path;
path.push_back("notifications");
path.push_back(nfcname);
ExpressionList::Ptr nfc_exprl = boost::make_shared<ExpressionList>();
item->GetLinkedExpressionList()->ExtractPath(path, nfc_exprl);
std::vector<String> dpath;
dpath.push_back("templates");
nfc_exprl->ErasePath(dpath);
builder->AddExpressionList(nfc_exprl);
ConfigItem::Ptr notificationItem = builder->Compile();