"loggers" object) moved inside the configuration objects (maps) for the
respective Kea modules. For example: the "Dhcp4" map contains the
"loggers" object specifying logging configuration for the DHCPv4
- server. Backward compatibility is maintained until at least Kea 1.7.0
+ server. Backward compatibility is maintained until Kea 1.7.8
release; it will be possible to specify the "Logging" object at the top
configuration level and "loggers" objects at the module configuration
- level. Ultimately, support for the top-level "Logging" object will be
- removed.
+ level. Finally, support for the top-level "Logging" object was
+ removed in Kea 1.7.0.
The specification of several supported elements (e.g. "Dhcp4",
"Dhcp6") in a single configuration file can be confusing and works
In the current Kea release it is possible to specify configurations
of multiple modules within a single configuration file, but this is
- not recommended and support for it will be removed in a future
- release. The only object, besides the one specifying module
- configuration, which can be (and usually was) included in the same file
- is ``Logging``. However, we don't include this object in the example
- above for clarity; its content, the list of loggers, should now be
- inside the ``Dhcp4`` object instead of the deprecated object.
+ not recommended and support for it was removed in 1.7.9 release,
+ including the ``Logging`` object: its previous content, the list
+ of loggers, must now inside be the ``Dhcp4`` object.
The Dhcp4 configuration starts with the ``"Dhcp4": {`` line and ends
with the corresponding closing brace (in the above example, the brace
In the current Kea release it is possible to specify configurations
of multiple modules within a single configuration file, but this is
- not recommended and support for it will be removed in a future
- release. The only object, besides the one specifying module
- configuration, which can be (and usually was) included in the same file
- is ``Logging``. However, we don't include this object in the example
- above for clarity; its content, the list of loggers, should now be
- inside the ``Dhcp6`` object instead of this deprecated object.
+ not recommended and support for it was removed in 1.7.9 release,
+ including the ``Logging`` object: its previous content, the list
+ of loggers, must now inside be the ``Dhcp6`` object.
The Dhcp6 configuration starts with the ``"Dhcp6": {`` line and ends
with the corresponding closing brace (in the above example, the brace
The logging system in Kea is configured through the loggers entry in the
server section of your configuration file. In previous Kea releases this
-entry was in an independent Logging section; this is still supported for
-backward compatibility.
+entry was in an independent Logging section; this was still supported
+for backward compatibility until Kea 1.7.8 included.
Loggers
-------
// Command arguments are expected to be:
// { "Dhcp4": { ... } }
- // The Logging component is supported by backward compatiblity.
if (!args) {
message = "Missing mandatory 'arguments' parameter.";
} else {
}
}
+ // Check unsupported objects.
+ if (message.empty()) {
+ for (auto obj : args->mapValue()) {
+ const string& obj_name = obj.first;
+ if (obj_name != "Dhcp4") {
+ LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_UNSUPPORTED_OBJECT)
+ .arg(obj_name);
+ if (message.empty()) {
+ message = "Unsupported '" + obj_name + "' parameter";
+ } else {
+ message += " (and '" + obj_name + "')";
+ }
+ }
+ }
+ if (!message.empty()) {
+ message += ".";
+ }
+ }
+
if (!message.empty()) {
// Something is amiss with arguments, return a failure response.
ConstElementPtr result = isc::config::createAnswer(status_code,
// configuration attempts.
CfgMgr::instance().rollback();
- // Check deprecated, obsolete or unknown (aka unsupported) objects.
- list<string> unsupported;
- for (auto obj : args->mapValue()) {
- const string& obj_name = obj.first;
- if ((obj_name == "Dhcp4") || (obj_name == "Logging")) {
- continue;
- }
- unsupported.push_back(obj_name);
- }
-
- // Relocate Logging: if there is a global Logging object takes its
- // loggers entry, move the entry to Dhcp4 and remove now empty Logging.
- Daemon::relocateLogging(args, "Dhcp4");
-
// Parse the logger configuration explicitly into the staging config.
// Note this does not alter the current loggers, they remain in
// effect until we apply the logging config below. If no logging
// out what exactly is wrong with the new config in case of problems.
CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
- // Log unsupported objects.
- if (!unsupported.empty()) {
- for (auto name : unsupported) {
- LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_UNSUPPORTED_OBJECT).arg(name);
- }
-
- // Will return an error in a future version.
- }
-
// Now we configure the server proper.
ConstElementPtr result = processConfig(dhcp4);
}
}
+ // Check unsupported objects.
+ if (message.empty()) {
+ for (auto obj : args->mapValue()) {
+ const string& obj_name = obj.first;
+ if (obj_name != "Dhcp4") {
+ LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_UNSUPPORTED_OBJECT)
+ .arg(obj_name);
+ if (message.empty()) {
+ message = "Unsupported '" + obj_name + "' parameter";
+ } else {
+ message += " (and '" + obj_name + "')";
+ }
+ }
+ }
+ if (!message.empty()) {
+ message += ".";
+ }
+ }
+
if (!message.empty()) {
// Something is amiss with arguments, return a failure response.
ConstElementPtr result = isc::config::createAnswer(status_code,
return (result);
}
- // Check obsolete objects.
-
- // Relocate Logging. Note this allows to check the loggers configuration.
- Daemon::relocateLogging(args, "Dhcp4");
-
- // Log obsolete objects and return an error.
-
// stop thread pool (if running)
MultiThreadingCriticalSection cs;
return isc::dhcp::Dhcp4Parser::make_SUB_DHCP_DDNS(driver.loc_);
case Parser4Context::PARSER_CONFIG_CONTROL:
return isc::dhcp::Dhcp4Parser::make_SUB_CONFIG_CONTROL(driver.loc_);
- case Parser4Context::PARSER_LOGGING:
- return isc::dhcp::Dhcp4Parser::make_SUB_LOGGING(driver.loc_);
}
}
%}
}
-\"Logging\" {
- switch(driver.ctx_) {
- case isc::dhcp::Parser4Context::CONFIG:
- return isc::dhcp::Dhcp4Parser::make_LOGGING(driver.loc_);
- default:
- return isc::dhcp::Dhcp4Parser::make_STRING("Logging", driver.loc_);
- }
-}
-
\"loggers\" {
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::DHCP4:
- case isc::dhcp::Parser4Context::LOGGING:
return isc::dhcp::Dhcp4Parser::make_LOGGERS(driver.loc_);
default:
return isc::dhcp::Dhcp4Parser::make_STRING("loggers", driver.loc_);
return isc::dhcp::Dhcp4Parser::make_STRING(tmp, driver.loc_);
}
-\"Dhcp6\" {
- switch(driver.ctx_) {
- case isc::dhcp::Parser4Context::CONFIG:
- return isc::dhcp::Dhcp4Parser::make_DHCP6(driver.loc_);
- default:
- return isc::dhcp::Dhcp4Parser::make_STRING("Dhcp6", driver.loc_);
- }
-}
-
-\"DhcpDdns\" {
- switch(driver.ctx_) {
- case isc::dhcp::Parser4Context::CONFIG:
- return isc::dhcp::Dhcp4Parser::make_DHCPDDNS(driver.loc_);
- default:
- return isc::dhcp::Dhcp4Parser::make_STRING("DhcpDdns", driver.loc_);
- }
-}
-
-\"Control-agent\" {
- switch(driver.ctx_) {
- case isc::dhcp::Parser4Context::CONFIG:
- return isc::dhcp::Dhcp4Parser::make_CONTROL_AGENT(driver.loc_);
- default:
- return isc::dhcp::Dhcp4Parser::make_STRING("Control-agent", driver.loc_);
- }
-}
-
\"4o6-interface\" {
switch(driver.ctx_) {
case isc::dhcp::Parser4Context::SUBNET4:
HOSTNAME_CHAR_SET "hostname-char-set"
HOSTNAME_CHAR_REPLACEMENT "hostname-char-replacement"
- LOGGING "Logging"
LOGGERS "loggers"
OUTPUT_OPTIONS "output_options"
OUTPUT "output"
MAXVER "maxver"
PATTERN "pattern"
- DHCP6 "Dhcp6"
- DHCPDDNS "DhcpDdns"
- CONTROL_AGENT "Control-agent"
-
// Not real tokens, just a way to signal what the parser is expected to
// parse.
TOPLEVEL_JSON
SUB_OPTION_DATA
SUB_HOOKS_LIBRARY
SUB_DHCP_DDNS
- SUB_LOGGING
SUB_CONFIG_CONTROL
;
| SUB_OPTION_DATA { ctx.ctx_ = ctx.OPTION_DATA; } sub_option_data
| SUB_HOOKS_LIBRARY { ctx.ctx_ = ctx.HOOKS_LIBRARIES; } sub_hooks_library
| SUB_DHCP_DDNS { ctx.ctx_ = ctx.DHCP_DDNS; } sub_dhcp_ddns
- | SUB_LOGGING { ctx.ctx_ = ctx.LOGGING; } sub_logging
| SUB_CONFIG_CONTROL { ctx.ctx_ = ctx.CONFIG_CONTROL; } sub_config_control
;
};
-// This defines the top-level { } that holds Control-agent, Dhcp6, Dhcp4,
-// DhcpDdns or Logging objects.
+// This defines the top-level { } that holds Dhcp4 only object.
syntax_map: LCURLY_BRACKET {
// This code is executed when we're about to start parsing
// the content of the map
ctx.require("Dhcp4", ctx.loc2pos(@1), ctx.loc2pos(@4));
};
-// This represents top-level entries: Control-agent, Dhcp6, Dhcp4,
-// DhcpDdns, Logging
+// This represents top-level entries: Dhcp4
global_objects: global_object
| global_objects COMMA global_object
;
-// This represents a single top level entry, e.g. Dhcp4 or DhcpDdns.
+// This represents a single top level entry, e.g. Dhcp4.
global_object: dhcp4_object
- | logging_object
- | dhcp6_json_object
- | dhcpddns_json_object
- | control_agent_json_object
- | unknown_map_entry
;
dhcp4_object: DHCP4 {
// This code is executed when we're about to start parsing
// the content of the map
+ // Prevent against duplicate.
+ ctx.unique("Dhcp4", ctx.loc2pos(@1));
ElementPtr m(new MapElement(ctx.loc2pos(@1)));
ctx.stack_.back()->set("Dhcp4", m);
ctx.stack_.push_back(m);
};
-// JSON entries for Dhcp4 and DhcpDdns
-
-dhcp6_json_object: DHCP6 {
- ctx.enter(ctx.NO_KEYWORD);
-} COLON value {
- ctx.stack_.back()->set("Dhcp6", $4);
- ctx.leave();
-};
-
-dhcpddns_json_object: DHCPDDNS {
- ctx.enter(ctx.NO_KEYWORD);
-} COLON value {
- ctx.stack_.back()->set("DhcpDdns", $4);
- ctx.leave();
-};
-
-control_agent_json_object: CONTROL_AGENT {
- ctx.enter(ctx.NO_KEYWORD);
-} COLON value {
- ctx.stack_.back()->set("Control-agent", $4);
- ctx.leave();
-};
-
// Config control information element
config_control: CONFIG_CONTROL {
ctx.stack_.back()->set("config-fetch-wait-time", value);
};
-// --- logging entry -----------------------------------------
-
-// This defines the top level "Logging" object. It parses
-// the following "Logging": { ... }. The ... is defined
-// by logging_params
-logging_object: LOGGING {
- ElementPtr m(new MapElement(ctx.loc2pos(@1)));
- ctx.stack_.back()->set("Logging", m);
- ctx.stack_.push_back(m);
- ctx.enter(ctx.LOGGING);
-} COLON LCURLY_BRACKET logging_params RCURLY_BRACKET {
- ctx.stack_.pop_back();
- ctx.leave();
-};
-
-sub_logging: LCURLY_BRACKET {
- // Parse the Logging map
- ElementPtr m(new MapElement(ctx.loc2pos(@1)));
- ctx.stack_.push_back(m);
-} logging_params RCURLY_BRACKET {
- // parsing completed
-};
-
-// This defines the list of allowed parameters that may appear
-// in the top-level Logging object. It can either be a single
-// parameter or several parameters separated by commas.
-logging_params: logging_param
- | logging_params COMMA logging_param
- ;
-
-// There's currently only one parameter defined, which is "loggers".
-logging_param: loggers;
+// --- loggers entry -----------------------------------------
-// "loggers", the only parameter currently defined in "Logging" object,
-// is "loggers": [ ... ].
loggers: LOGGERS {
ElementPtr l(new ListElement(ctx.loc2pos(@1)));
ctx.stack_.back()->set("loggers", l);
| loggers_entries COMMA logger_entry
;
-// This defines a single entry defined in loggers in Logging.
+// This defines a single entry defined in loggers.
logger_entry: LCURLY_BRACKET {
ElementPtr l(new MapElement(ctx.loc2pos(@1)));
ctx.stack_.back()->add(l);
}
}
+void
+Parser4Context::unique(const std::string& name,
+ isc::data::Element::Position loc)
+{
+ ConstElementPtr value = stack_.back()->get(name);
+ if (value) {
+ isc_throw(Dhcp4ParseError, loc << ": duplicate " << name
+ << " entries in " << contextName()
+ << " map (previous at " << value->getPosition() << ")");
+ }
+}
+
void
Parser4Context::enter(const ParserContext& ctx)
{
return ("toplevel");
case DHCP4:
return ("Dhcp4");
- case LOGGING:
- return ("Logging");
case INTERFACES_CONFIG:
return ("interfaces-config");
case DHCP_SOCKET_TYPE:
/// This will parse the input as config-control.
PARSER_CONFIG_CONTROL,
-
- /// This will parse the content of Logging.
- PARSER_LOGGING
} ParserType;
/// @brief Default constructor.
///
/// @param name name of the parameter to check
/// @param open_loc location of the opening curly bracket
- /// @param close_loc ocation of the closing curly bracket
+ /// @param close_loc location of the closing curly bracket
/// @throw Dhcp4ParseError
void require(const std::string& name,
isc::data::Element::Position open_loc,
isc::data::Element::Position close_loc);
+ /// @brief Check if a parameter is already present
+ ///
+ /// Check if a parameter is already present in the map at the top
+ /// of the stack and raise an error when it is.
+ ///
+ /// @param name name of the parameter to check
+ /// @param loc location of the current parameter
+ /// @throw Dhcp4ParseError
+ void unique(const std::string& name,
+ isc::data::Element::Position loc);
+
/// @brief Defines syntactic contexts for lexical tie-ins
typedef enum {
///< This one is used in pure JSON mode.
NO_KEYWORD,
- ///< Used while parsing top level (that contains Dhcp4, Logging and others)
+ ///< Used while parsing top level (that contains Dhcp4)
CONFIG,
///< Used while parsing content of Dhcp4.
DHCP4,
- // not yet Dhcp6, DhcpDdns,
-
- ///< Used while parsing content of Logging
- LOGGING,
-
/// Used while parsing Dhcp4/interfaces structures.
INTERFACES_CONFIG,
string control_socket_footer =
"\" \n} \n";
string logger_txt =
- " \"Logging\": { \n"
- " \"loggers\": [ { \n"
+ " ,\"loggers\": [ { \n"
" \"name\": \"kea\", \n"
" \"severity\": \"FATAL\", \n"
" \"output_options\": [{ \n"
" \"output\": \"/dev/null\" \n"
" }] \n"
- " }] \n"
- " } \n";
+ " }] \n";
std::ostringstream os;
<< control_socket_header
<< socket_path_
<< control_socket_footer
- << "}\n" // close dhcp4
- << ","
<< logger_txt
+ << "}\n" // close dhcp4
<< "}}";
// Send the config-set command
<< socket_path_
<< control_socket_footer
<< "}\n" // close dhcp4
- "}}";
+ << "}}";
// Send the config-set command
sendUnixCommand(os.str(), response);
string control_socket_footer =
"\" \n} \n";
string logger_txt =
- " \"Logging\": { \n"
- " \"loggers\": [ { \n"
+ " ,\"loggers\": [ { \n"
" \"name\": \"kea\", \n"
" \"severity\": \"FATAL\", \n"
" \"output_options\": [{ \n"
" \"output\": \"/dev/null\" \n"
" }] \n"
- " }] \n"
- " } \n";
+ " }] \n";
std::ostringstream os;
<< control_socket_header
<< socket_path_
<< control_socket_footer
- << "}\n" // close dhcp4
- << ","
<< logger_txt
+ << "}\n" // close dhcp4
<< "}}";
// Send the config-set command
<< socket_path_
<< control_socket_footer
<< "}\n" // close dhcp4
- "}}";
+ << "}}";
// Send the config-test command
sendUnixCommand(os.str(), response);
\"dhcp-ddns\": {
\"enable-updates\": true,
\"qualifying-suffix\": \"\"
- }
- },
-
- \"Logging\":
- {
+ },
\"loggers\": [
{
\"name\": \"kea-dhcp4\",
{
\"subnet\": \"10.0.0.0/8\",
\"pool\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ]
- } ]
- },
-
- \"Logging\":
- {
+ } ],
\"loggers\": [
{
\"name\": \"kea-dhcp4\",
{
\"subnet\": \"10.0.0.0/8\",
\"pools\": [ { \"pool\": \"192.168.0.10-192.168.0.100\" } ]
- } ]
- },
-
- \"Logging\":
- {
+ } ],
\"loggers\": [
{
\"name\": \"kea-dhcp4\",
{
\"subnet\": \"10.0.0.0/8\",
\"pool\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ]
- } ]
- },
-
- \"Logging\":
- {
+ } ],
\"loggers\": [
{
\"name\": \"kea-dhcp4\",
EXPECT_EQ(Lease::TYPE_V4, pools3.at(0)->getType());
}
-// This test verifies that the configurations for various servers
-// can coexist and that the DHCPv4 configuration parsers will simply
-// ignore them.
-TEST_F(JSONFileBackendTest, serverConfigurationsCoexistence) {
- std::string config = "{ \"Dhcp4\": {"
- "\"rebind-timer\": 2000, "
- "\"renew-timer\": 1000, \n"
- "\"valid-lifetime\": 4000 }, "
- "\"Dhcp6\": { },"
- "\"DhcpDdns\": { },"
- "\"Control-agent\": { }"
- "}";
-
- writeFile(TEST_FILE, config);
-
- // Now initialize the server
- boost::scoped_ptr<ControlledDhcpv4Srv> srv;
- ASSERT_NO_THROW(
- srv.reset(new ControlledDhcpv4Srv(0))
- );
-
- // And configure it using the config file.
- EXPECT_NO_THROW(srv->init(TEST_FILE));
-}
-
// This test checks if configuration can be read from a JSON file
// using hash (#) line comments
TEST_F(JSONFileBackendTest, hashComments) {
"expecting }");
testError("{ 123 }\n",
Parser4Context::PARSER_DHCP4,
- "<string>:1.3-5: syntax error, unexpected integer");
+ "<string>:1.3-5: syntax error, unexpected integer, "
+ "expecting Dhcp4");
testError("{ \"foo\" }\n",
Parser4Context::PARSER_JSON,
"<string>:1.9: syntax error, unexpected }, "
"expecting :");
testError("{ \"foo\" }\n",
Parser4Context::PARSER_DHCP4,
- "<string>:1.9: syntax error, unexpected }, expecting :");
+ "<string>:1.3-7: syntax error, unexpected constant string, "
+ "expecting Dhcp4");
testError("{ \"foo\":null }\n",
Parser4Context::PARSER_DHCP4,
- "<string>:1.3-7: got unexpected keyword "
- "\"foo\" in toplevel map.");
+ "<string>:1.3-7: syntax error, unexpected constant string, "
+ "expecting Dhcp4");
+ testError("{ \"Logging\":null }\n",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:1.3-11: syntax error, unexpected constant string, "
+ "expecting Dhcp4");
testError("{ \"Dhcp4\" }\n",
Parser4Context::PARSER_DHCP4,
"<string>:1.11: syntax error, unexpected }, "
"expecting :");
- testError("{ \"Dhcp6\":[]\n",
- Parser4Context::PARSER_DHCP4,
- "<string>:2.1: syntax error, unexpected end of file, "
- "expecting \",\" or }");
testError("{}{}\n",
Parser4Context::PARSER_JSON,
"<string>:1.3: syntax error, unexpected {, "
Parser4Context::PARSER_DHCP4,
"<string>:3.3-11: duplicate user-context/comment entries "
"(previous at <string>:2:19)");
+
+ // duplicate Dhcp4 entries
+ testError("{ \"Dhcp4\":{\n"
+ " \"comment\": \"first\" },\n"
+ " \"Dhcp4\":{\n"
+ " \"comment\": \"second\" }}\n",
+ Parser4Context::PARSER_DHCP4,
+ "<string>:3:3: duplicate Dhcp4 entries in toplevel map "
+ "(previous at <string>:1:3)");
}
// Check unicode escapes
EXPECT_EQ("////", result->stringValue());
}
-};
-};
-};
+}
+}
+}
}
}
+ // Check unsupported objects.
+ if (message.empty()) {
+ for (auto obj : args->mapValue()) {
+ const string& obj_name = obj.first;
+ if (obj_name != "Dhcp6") {
+ LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_UNSUPPORTED_OBJECT)
+ .arg(obj_name);
+ if (message.empty()) {
+ message = "Unsupported '" + obj_name + "' parameter";
+ } else {
+ message += " (and '" + obj_name + "')";
+ }
+ }
+ }
+ if (!message.empty()) {
+ message += ".";
+ }
+ }
+
if (!message.empty()) {
// Something is amiss with arguments, return a failure response.
ConstElementPtr result = isc::config::createAnswer(status_code,
// configuration attempts.
CfgMgr::instance().rollback();
- // Check deprecated, obsolete or unknown (aka unsupported) objects.
- list<string> unsupported;
- for (auto obj : args->mapValue()) {
- const string& obj_name = obj.first;
- if ((obj_name == "Dhcp6") || (obj_name == "Logging")) {
- continue;
- }
- unsupported.push_back(obj_name);
- }
-
- // Relocate Logging: if there is a global Logging object takes its
- // loggers entry, move the entry to Dhcp6 and remove now empty Logging.
- Daemon::relocateLogging(args, "Dhcp6");
-
// Parse the logger configuration explicitly into the staging config.
// Note this does not alter the current loggers, they remain in
// effect until we apply the logging config below. If no logging
// out what exactly is wrong with the new config in case of problems.
CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
- // Log unsupported objects.
- if (!unsupported.empty()) {
- for (auto name : unsupported) {
- LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_UNSUPPORTED_OBJECT).arg(name);
- }
-
- // Will return an error in a future version.
- }
-
// Now we configure the server proper.
ConstElementPtr result = processConfig(dhcp6);
}
}
+ // Check unsupported objects.
+ if (message.empty()) {
+ for (auto obj : args->mapValue()) {
+ const string& obj_name = obj.first;
+ if (obj_name != "Dhcp6") {
+ LOG_ERROR(dhcp6_logger, DHCP6_CONFIG_UNSUPPORTED_OBJECT)
+ .arg(obj_name);
+ if (message.empty()) {
+ message = "Unsupported '" + obj_name + "' parameter";
+ } else {
+ message += " (and '" + obj_name + "')";
+ }
+ }
+ }
+ if (!message.empty()) {
+ message += ".";
+ }
+ }
+
if (!message.empty()) {
// Something is amiss with arguments, return a failure response.
ConstElementPtr result = isc::config::createAnswer(status_code,
return (result);
}
- // Check obsolete objects.
-
- // Relocate Logging. Note this allows to check the loggers configuration.
- Daemon::relocateLogging(args, "Dhcp6");
-
- // Log obsolete objects and return an error.
-
// stop thread pool (if running)
MultiThreadingCriticalSection cs;
return isc::dhcp::Dhcp6Parser::make_SUB_DHCP_DDNS(driver.loc_);
case Parser6Context::PARSER_CONFIG_CONTROL:
return isc::dhcp::Dhcp6Parser::make_SUB_CONFIG_CONTROL(driver.loc_);
- case Parser6Context::PARSER_LOGGING:
- return isc::dhcp::Dhcp6Parser::make_SUB_LOGGING(driver.loc_);
}
}
%}
}
}
-\"Logging\" {
- switch(driver.ctx_) {
- case isc::dhcp::Parser6Context::CONFIG:
- return isc::dhcp::Dhcp6Parser::make_LOGGING(driver.loc_);
- default:
- return isc::dhcp::Dhcp6Parser::make_STRING("Logging", driver.loc_);
- }
-}
-
\"loggers\" {
switch(driver.ctx_) {
case isc::dhcp::Parser6Context::DHCP6:
- case isc::dhcp::Parser6Context::LOGGING:
return isc::dhcp::Dhcp6Parser::make_LOGGERS(driver.loc_);
default:
return isc::dhcp::Dhcp6Parser::make_STRING("loggers", driver.loc_);
}
}
-\"Dhcp4\" {
- switch(driver.ctx_) {
- case isc::dhcp::Parser6Context::CONFIG:
- return isc::dhcp::Dhcp6Parser::make_DHCP4(driver.loc_);
- default:
- return isc::dhcp::Dhcp6Parser::make_STRING("Dhcp4", driver.loc_);
- }
-}
-
-\"DhcpDdns\" {
- switch(driver.ctx_) {
- case isc::dhcp::Parser6Context::CONFIG:
- return isc::dhcp::Dhcp6Parser::make_DHCPDDNS(driver.loc_);
- default:
- return isc::dhcp::Dhcp6Parser::make_STRING("DhcpDdns", driver.loc_);
- }
-}
-
-\"Control-agent\" {
- switch(driver.ctx_) {
- case isc::dhcp::Parser6Context::CONFIG:
- return isc::dhcp::Dhcp6Parser::make_CONTROL_AGENT(driver.loc_);
- default:
- return isc::dhcp::Dhcp6Parser::make_STRING("Control-agent", driver.loc_);
- }
-}
-
{JSONString} {
/* A string has been matched. It contains the actual string and single quotes.
We need to get those quotes out of the way and just use its content, e.g.
HOSTNAME_CHAR_SET "hostname-char-set"
HOSTNAME_CHAR_REPLACEMENT "hostname-char-replacement"
- LOGGING "Logging"
LOGGERS "loggers"
OUTPUT_OPTIONS "output_options"
OUTPUT "output"
MAXVER "maxver"
PATTERN "pattern"
- DHCP4 "Dhcp4"
- DHCPDDNS "DhcpDdns"
- CONTROL_AGENT "Control-agent"
-
// Not real tokens, just a way to signal what the parser is expected to
// parse.
TOPLEVEL_JSON
SUB_OPTION_DATA
SUB_HOOKS_LIBRARY
SUB_DHCP_DDNS
- SUB_LOGGING
SUB_CONFIG_CONTROL
;
| SUB_OPTION_DATA { ctx.ctx_ = ctx.OPTION_DATA; } sub_option_data
| SUB_HOOKS_LIBRARY { ctx.ctx_ = ctx.HOOKS_LIBRARIES; } sub_hooks_library
| SUB_DHCP_DDNS { ctx.ctx_ = ctx.DHCP_DDNS; } sub_dhcp_ddns
- | SUB_LOGGING { ctx.ctx_ = ctx.LOGGING; } sub_logging
| SUB_CONFIG_CONTROL { ctx.ctx_ = ctx.CONFIG_CONTROL; } sub_config_control
;
};
-// This defines the top-level { } that holds Control-agent, Dhcp6, Dhcp4,
-// DhcpDdns or Logging objects.
+// This defines the top-level { } that holds Dhcp6 only object.
syntax_map: LCURLY_BRACKET {
// This code is executed when we're about to start parsing
// the content of the map
ctx.require("Dhcp6", ctx.loc2pos(@1), ctx.loc2pos(@4));
};
-// This represents top-level entries: Control-agent, Dhcp6, Dhcp4,
-// DhcpDdns, Logging
+// This represents top-level entries: Dhcp6
global_objects: global_object
| global_objects COMMA global_object
;
-// This represents a single top level entry, e.g. Dhcp6 or DhcpDdns.
+// This represents a single top level entry, e.g. Dhcp6.
global_object: dhcp6_object
- | logging_object
- | dhcp4_json_object
- | dhcpddns_json_object
- | control_agent_json_object
- | unknown_map_entry
;
dhcp6_object: DHCP6 {
// This code is executed when we're about to start parsing
// the content of the map
+ // Prevent against duplicate.
+ ctx.unique("Dhcp6", ctx.loc2pos(@1));
ElementPtr m(new MapElement(ctx.loc2pos(@1)));
ctx.stack_.back()->set("Dhcp6", m);
ctx.stack_.push_back(m);
};
-// JSON entries for Dhcp4 and DhcpDdns
-
-dhcp4_json_object: DHCP4 {
- ctx.enter(ctx.NO_KEYWORD);
-} COLON value {
- ctx.stack_.back()->set("Dhcp4", $4);
- ctx.leave();
-};
-
-dhcpddns_json_object: DHCPDDNS {
- ctx.enter(ctx.NO_KEYWORD);
-} COLON value {
- ctx.stack_.back()->set("DhcpDdns", $4);
- ctx.leave();
-};
-
-control_agent_json_object: CONTROL_AGENT {
- ctx.enter(ctx.NO_KEYWORD);
-} COLON value {
- ctx.stack_.back()->set("Control-agent", $4);
- ctx.leave();
-};
-
// Config control information element
config_control: CONFIG_CONTROL {
ctx.stack_.back()->set("config-fetch-wait-time", value);
};
-// --- logging entry -----------------------------------------
-
-// This defines the top level "Logging" object. It parses
-// the following "Logging": { ... }. The ... is defined
-// by logging_params
-logging_object: LOGGING {
- ElementPtr m(new MapElement(ctx.loc2pos(@1)));
- ctx.stack_.back()->set("Logging", m);
- ctx.stack_.push_back(m);
- ctx.enter(ctx.LOGGING);
-} COLON LCURLY_BRACKET logging_params RCURLY_BRACKET {
- ctx.stack_.pop_back();
- ctx.leave();
-};
-
-sub_logging: LCURLY_BRACKET {
- // Parse the Logging map
- ElementPtr m(new MapElement(ctx.loc2pos(@1)));
- ctx.stack_.push_back(m);
-} logging_params RCURLY_BRACKET {
- // parsing completed
-};
-
-// This defines the list of allowed parameters that may appear
-// in the top-level Logging object. It can either be a single
-// parameter or several parameters separated by commas.
-logging_params: logging_param
- | logging_params COMMA logging_param
- ;
-
-// There's currently only one parameter defined, which is "loggers".
-logging_param: loggers;
+// --- loggers entry -----------------------------------------
-// "loggers", the only parameter currently defined in "Logging" object,
-// is "loggers": [ ... ].
loggers: LOGGERS {
ElementPtr l(new ListElement(ctx.loc2pos(@1)));
ctx.stack_.back()->set("loggers", l);
| loggers_entries COMMA logger_entry
;
-// This defines a single entry defined in loggers in Logging.
+// This defines a single entry defined in loggers.
logger_entry: LCURLY_BRACKET {
ElementPtr l(new MapElement(ctx.loc2pos(@1)));
ctx.stack_.back()->add(l);
}
}
+void
+Parser6Context::unique(const std::string& name,
+ isc::data::Element::Position loc)
+{
+ ConstElementPtr value = stack_.back()->get(name);
+ if (value) {
+ isc_throw(Dhcp6ParseError, loc << ": duplicate " << name
+ << " entries in " << contextName()
+ << " map (previous at " << value->getPosition() << ")");
+ }
+}
+
void
Parser6Context::enter(const ParserContext& ctx)
{
return ("toplevel");
case DHCP6:
return ("Dhcp6");
- case LOGGING:
- return ("Logging");
case INTERFACES_CONFIG:
return ("interfaces-config");
case LEASE_DATABASE:
/// This will parse the input as config-control.
PARSER_CONFIG_CONTROL,
-
- /// This will parse the content of Logging.
- PARSER_LOGGING
-
} ParserType;
/// @brief Default constructor.
isc::data::Element::Position open_loc,
isc::data::Element::Position close_loc);
+ /// @brief Check if a parameter is already present
+ ///
+ /// Check if a parameter is already present in the map at the top
+ /// of the stack and raise an error when it is.
+ ///
+ /// @param name name of the parameter to check
+ /// @param loc location of the current parameter
+ /// @throw Dhcp6ParseError
+ void unique(const std::string& name,
+ isc::data::Element::Position loc);
+
/// @brief Defines syntactic contexts for lexical tie-ins
typedef enum {
///< This one is used in pure JSON mode.
NO_KEYWORD,
- ///< Used while parsing top level (that contains Dhcp6, Logging and others)
+ ///< Used while parsing top level (that contains Dhcp6)
CONFIG,
///< Used while parsing content of Dhcp6.
DHCP6,
- // not yet Dhcp4, DhcpDdns,
-
- ///< Used while parsing content of Logging
- LOGGING,
-
/// Used while parsing Dhcp6/interfaces structures.
INTERFACES_CONFIG,
string control_socket_footer =
"\" \n} \n";
string logger_txt =
- " \"Logging\": { \n"
- " \"loggers\": [ { \n"
+ " ,\"loggers\": [ { \n"
" \"name\": \"kea\", \n"
" \"severity\": \"FATAL\", \n"
" \"output_options\": [{ \n"
<< control_socket_header
<< socket_path_
<< control_socket_footer
- << "}\n" // close dhcp6
- << ","
<< logger_txt
+ << "}\n" // close dhcp6
<< "}}";
// Send the config-set command
string control_socket_footer =
"\" \n} \n";
string logger_txt =
- " \"Logging\": { \n"
- " \"loggers\": [ { \n"
+ " ,\"loggers\": [ { \n"
" \"name\": \"kea\", \n"
" \"severity\": \"FATAL\", \n"
" \"output_options\": [{ \n"
<< control_socket_header
<< socket_path_
<< control_socket_footer
- << "}\n" // close dhcp6
- << ","
<< logger_txt
+ << "}\n" // close dhcp6
<< "}}";
// Send the config-set command
\"dhcp-ddns\": {
\"enable-updates\": true,
\"qualifying-suffix\": \"\"
- }
- },
-
- \"Logging\":
- {
+ },
\"loggers\": [
{
\"name\": \"kea-dhcp6\",
{
\"subnet\": \"2001:db8:1::/64\",
\"pool\": [ { \"pool\": \"2001:db8:1::10-2001:db8:1::100\" } ]
- } ]
- },
-
- \"Logging\":
- {
+ } ],
\"loggers\": [
{
\"name\": \"kea-dhcp6\",
{
\"subnet\": \"2001:db8:1::/64\",
\"pool\": [ { \"pool\": \"2001:db8:1::10-2001:db8:1::100\" } ]
- } ]
- },
-
- \"Logging\":
- {
+ } ],
\"loggers\": [
{
\"name\": \"kea-dhcp6\",
EXPECT_EQ(Lease::TYPE_NA, pools3.at(0)->getType());
}
-// This test verifies that the configurations for various servers
-// can coexist and that the DHCPv6 configuration parsers will simply
-// ignore them.
-TEST_F(JSONFileBackendTest, serverConfigurationsCoexistence) {
- std::string config = "{ \"Dhcp6\": {"
- "\"rebind-timer\": 2000, "
- "\"renew-timer\": 1000, \n"
- "\"preferred-lifetime\": 1000, \n"
- "\"valid-lifetime\": 4000 }, "
- "\"Dhcp4\": { },"
- "\"DhcpDdns\": { },"
- "\"Control-agent\": { }"
- "}";
-
- writeFile(TEST_FILE, config);
-
- // Now initialize the server
- boost::scoped_ptr<ControlledDhcpv6Srv> srv;
- ASSERT_NO_THROW(
- srv.reset(new ControlledDhcpv6Srv(0))
- );
-
- // And configure it using the config file.
- EXPECT_NO_THROW(srv->init(TEST_FILE));
-}
-
// This test checks if configuration can be read from a JSON file
// using hash (#) line comments
TEST_F(JSONFileBackendTest, hashComments) {
-// Copyright (C) 2016-2019 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2016-2020 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
"expecting }");
testError("{ 123 }\n",
Parser6Context::PARSER_DHCP6,
- "<string>:1.3-5: syntax error, unexpected integer");
+ "<string>:1.3-5: syntax error, unexpected integer, "
+ "expecting Dhcp6");
testError("{ \"foo\" }\n",
Parser6Context::PARSER_JSON,
"<string>:1.9: syntax error, unexpected }, "
"expecting :");
testError("{ \"foo\" }\n",
Parser6Context::PARSER_DHCP6,
- "<string>:1.9: syntax error, unexpected }, expecting :");
+ "<string>:1.3-7: syntax error, unexpected constant string, "
+ "expecting Dhcp6");
testError("{ \"foo\":null }\n",
Parser6Context::PARSER_DHCP6,
- "<string>:1.3-7: got unexpected keyword "
- "\"foo\" in toplevel map.");
+ "<string>:1.3-7: syntax error, unexpected constant string, "
+ "expecting Dhcp6");
+ testError("{ \"Logging\":null }\n",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:1.3-11: syntax error, unexpected constant string, "
+ "expecting Dhcp6");
testError("{ \"Dhcp6\" }\n",
Parser6Context::PARSER_DHCP6,
"<string>:1.11: syntax error, unexpected }, "
"expecting :");
- testError("{ \"Dhcp4\":[]\n",
- Parser6Context::PARSER_DHCP6,
- "<string>:2.1: syntax error, unexpected end of file, "
- "expecting \",\" or }");
testError("{}{}\n",
Parser6Context::PARSER_JSON,
"<string>:1.3: syntax error, unexpected {, "
Parser6Context::PARSER_DHCP6,
"<string>:3.3-11: duplicate user-context/comment entries "
"(previous at <string>:2:19)");
+
+ // duplicate Dhcp6 entries
+ testError("{ \"Dhcp6\":{\n"
+ " \"comment\": \"first\" },\n"
+ " \"Dhcp6\":{\n"
+ " \"comment\": \"second\" }}\n",
+ Parser6Context::PARSER_DHCP6,
+ "<string>:3:3: duplicate Dhcp6 entries in toplevel map "
+ "(previous at <string>:1:3)");
}
// Check unicode escapes
EXPECT_EQ("////", result->stringValue());
}
-};
-};
-};
+}
+}
+}
" include not map '" << getAppName() << "' entry");
}
- // Handle other (i.e. not application name) objects (e.g. Logging).
+ // Handle other (i.e. not application name) objects.
handleOtherObjects(whole_config);
// Get an application process object.
" include not map '" << getAppName() << "' entry");
}
- // Handle other (i.e. not application name) objects (e.g. Logging).
+ // Handle other (i.e. not application name) objects.
handleOtherObjects(whole_config);
// Let's configure logging before applying the configuration,
if (obj_name == app_name) {
continue;
}
- if (obj_name == "Logging") {
- LOG_WARN(dctl_logger, DCTL_CONFIG_DEPRECATED)
- .arg("The top level element, 'Logging', has been deprecated."
- " Loggers should be defined with the 'loggers[]'"
- " element within the '" + app_name + "' scope.");
- continue;
- }
- LOG_WARN(dctl_logger, DCTL_CONFIG_DEPRECATED)
+ LOG_ERROR(dctl_logger, DCTL_CONFIG_DEPRECATED)
.arg("'" + obj_name + "', defining anything in global level besides '"
+ app_name + "' is no longer supported.");
}
-
- // Relocate Logging: if there is a global Logging object takes its
- // loggers entry, move the entry to AppName object and remove
- // now empty Logging.
- Daemon::relocateLogging(args, app_name);
}
ConstElementPtr
return (result);
}
- // Handle other (i.e. not application name) objects (e.g. Logging).
+ // Handle other (i.e. not application name) objects.
handleOtherObjects(args);
// We are starting the configuration process so we should remove any
try {
- // Handle other (i.e. not application name) objects (e.g. Logging).
+ // Handle other (i.e. not application name) objects.
handleOtherObjects(args);
// We are starting the configuration process so we should remove any
///
/// Code shared between configuration handlers:
/// - check obsolete or unknown (aka unsupported) objects.
- /// - relocate Logging.
///
/// @param args Command arguments.
void handleOtherObjects(isc::data::ConstElementPtr args);
}
}
-void Daemon::relocateLogging(ConstElementPtr config,
- const std::string server_name) {
- ConstElementPtr logging = config->get("Logging");
- ConstElementPtr loggers;
- if (logging) {
- loggers = logging->get("loggers");
- ElementPtr mutable_cfg = boost::const_pointer_cast<Element>(config);
- mutable_cfg->remove("Logging");
- }
- if (loggers) {
- ConstElementPtr server = config->get(server_name);
- ElementPtr mutable_srv = boost::const_pointer_cast<Element>(server);
- mutable_srv->set("loggers", loggers);
- }
- while (config->size() > 1) {
- ElementPtr mutable_cfg = boost::const_pointer_cast<Element>(config);
- for (auto object : config->mapValue()) {
- if (object.first != server_name) {
- mutable_cfg->remove(object.first);
- break;
- }
- }
- }
-}
-
void Daemon::configureLogger(const ConstElementPtr& log_config,
const ConfigPtr& storage) {
/// @param verbose verbose mode (true usually enables DEBUG messages)
static void loggerInit(const char* log_name, bool verbose);
- /// @brief Relocate Logging configuration
- ///
- /// Moves the loggers entry from Logging to the server top element.
- /// This method assumes the configuration is sane, e.g. the server
- /// top element exists and is a map.
- /// Top elements other than the server one are removed.
- ///
- /// @param config JSON top level configuration
- /// @param server_name name of the server top element
- static void relocateLogging(isc::data::ConstElementPtr config,
- const std::string server_name);
-
/// @brief Configures logger
///
/// Applies configuration stored in a top-level structure in the
may be provided.
% DCTL_CONFIG_DEPRECATED server configuration includes a deprecated object: %1
-This warning message is issued when the configuration includes a deprecated
+This error message is issued when the configuration includes a deprecated
object (i.e. a top level element) which will be ignored.
% DCTL_CONFIG_FETCH Fetching configuration data from config backends.
EXPECT_EQ(errno, ENOENT);
}
-// Check that relocateLogging method is behaving properly with no Logging.
-TEST_F(DaemonTest, relocateLoggingNoLogging) {
- std::string config_txt = "{ \"myServer\": { } }";
- ConstElementPtr config = Element::fromJSON(config_txt);
- ConstElementPtr expected = Element::fromJSON(config_txt);
- Daemon x;
- EXPECT_NO_THROW(x.relocateLogging(config, "myServer"));
- EXPECT_TRUE(expected->equals(*config));
-}
-
-// Check that relocateLogging method is behaving properly with empty Logging.
-TEST_F(DaemonTest, relocateLoggingEmptyLogging) {
- std::string config_txt =
- "{ \"myServer\": { },\n"
- " \"Logging\": { } }";
- ConstElementPtr config = Element::fromJSON(config_txt);
- std::string expected_txt = "{ \"myServer\": { } }";
- ConstElementPtr expected = Element::fromJSON(expected_txt);
- Daemon x;
- EXPECT_NO_THROW(x.relocateLogging(config, "myServer"));
- EXPECT_TRUE(expected->equals(*config));
-}
-
-// Check that relocateLogging method is behaving properly.
-TEST_F(DaemonTest, relocateLogging) {
- std::string config_txt =
- "{ \"myServer\": { },\n"
- " \"Logging\": {\n"
- " \"loggers\": [ ] } }";
- ConstElementPtr config = Element::fromJSON(config_txt);
- std::string expected_txt =
- "{ \"myServer\": {\n"
- " \"loggers\": [ ] } }";
- ConstElementPtr expected = Element::fromJSON(expected_txt);
- Daemon x;
- EXPECT_NO_THROW(x.relocateLogging(config, "myServer"));
- EXPECT_TRUE(expected->equals(*config));
-}
-
-// Check that relocateLogging method is behaving properly with extra objects.
-TEST_F(DaemonTest, relocateLoggingExtraObjects) {
- std::string config_txt =
- "{ \"myServer\": { },\n"
- " \"Foobar\": { } }";
- ConstElementPtr config = Element::fromJSON(config_txt);
- std::string expected_txt = "{ \"myServer\": { } }";
- ConstElementPtr expected = Element::fromJSON(expected_txt);
- Daemon x;
- EXPECT_NO_THROW(x.relocateLogging(config, "myServer"));
- EXPECT_TRUE(expected->equals(*config));
-}
-
// Checks that configureLogger method is behaving properly.
// More dedicated tests are available for LogConfigParser class.
// See logger_unittest.cc