Commit 7c349367 authored by Francis Dupont's avatar Francis Dupont
Browse files

[master] Merged trac5152 (check D2 and CA config)

parents 82bbb6bd 80333c54
This diff is collapsed.
......@@ -84,7 +84,8 @@ public:
/// of an integer status value (0 means successful, non-zero means failure),
/// and a string explanation of the outcome.
virtual isc::data::ConstElementPtr
configure(isc::data::ConstElementPtr config_set, bool check_only);
configure(isc::data::ConstElementPtr config_set,
bool check_only = false);
/// @brief Processes the given command.
///
......
......@@ -52,7 +52,8 @@
<arg><option>-V</option></arg>
<arg><option>-W</option></arg>
<arg><option>-d</option></arg>
<arg><option>-s</option></arg>
<arg><option>-c<replaceable class="parameter">config-file</replaceable></option></arg>
<arg><option>-t<replaceable class="parameter">config-file</replaceable></option></arg>
</cmdsynopsis>
</refsynopsisdiv>
......@@ -115,6 +116,16 @@
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-t</option></term>
<listitem><para>
Check the syntax of the configuration file and report the
first error if any. Note that not all parameters are
completely checked, in particular, service and client
sockets are not opened, and hook libraries are not loaded.
</para></listitem>
</varlistentry>
</variablelist>
</refsect1>
......
......@@ -25,9 +25,18 @@ int main(int argc, char* argv[]) {
// 'false' value disables test mode.
controller->launch(argc, argv, false);
} catch (const VersionMessage& ex) {
std::cout << ex.what() << std::endl;
std::string msg(ex.what());
if (!msg.empty()) {
std::cout << msg << std::endl;
}
} catch (const InvalidUsage& ex) {
std::string msg(ex.what());
if (!msg.empty()) {
std::cerr << msg << std::endl;
}
ret = EXIT_FAILURE;
} catch (const isc::Exception& ex) {
std::cerr << "Service failed:" << ex.what() << std::endl;
std::cerr << "Service failed: " << ex.what() << std::endl;
ret = EXIT_FAILURE;
}
......
# Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
# Copyright (C) 2016-2017 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
......@@ -34,12 +34,55 @@ CONFIG="{
}
}"
# Invalid configuration (syntax error) to check that Kea can check syntax.
CONFIG_BAD_SYNTAX="{
\"Control-agent\":
{
\"http-port\": BOGUS
}
}"
# Invalid configuration (out of range port) to check that Kea can check syntax.
CONFIG_BAD_VALUE="{
\"Control-agent\":
{
\"http-port\": 80000
}
}"
bin="kea-ctrl-agent"
bin_path=@abs_top_builddir@/src/bin/agent
# Import common test library.
. @abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh
# This test verifies that syntax checking works properly. This function
# requires 3 parameters:
# testname
# config - string with a content of the config (will be written to a file)
# exp_code - expected exit code returned by kea (0 - success, 1 - failure)
syntax_check_test() {
local TESTNAME="${1}"
local CONFIG="${2}"
local EXP_CODE="${3}"
# Log the start of the test and print test name.
test_start $TESTNAME
# Remove dangling Kea instances and remove log files.
cleanup
# Create correct configuration file.
create_config "${CONFIG}"
# Check it
printf "Running command %s.\n" "\"${bin_path}/${bin} -t ${CFG_FILE}\""
${bin_path}/${bin} -t ${CFG_FILE}
exit_code=$?
if [ ${exit_code} -ne $EXP_CODE ]; then
printf "ERROR: expected exit code ${EXP_CODE}, got ${exit_code}\n"
clean_exit 1
fi
test_finish 0
}
# This test verifies that Control Agent is shut down gracefully when it
# receives a SIGINT or SIGTERM signal.
shutdown_test() {
......@@ -102,4 +145,6 @@ shutdown_test() {
server_pid_file_test "${CONFIG}" DCTL_ALREADY_RUNNING
shutdown_test "ctrl-agent.sigterm_test" 15
shutdown_test "ctrl-agent.sigint_test" 2
syntax_check_test "ctrl-agent.syntax_check_success" "${CONFIG}" 0
syntax_check_test "ctrl-agent.syntax_check_bad_syntax" "${CONFIG_BAD_SYNTAX}" 1
syntax_check_test "ctrl-agent.syntax_check_bad_values" "${CONFIG_BAD_VALUE}" 1
......@@ -37,7 +37,7 @@ to shutdown and has met the required criteria to exit.
This is a debug message issued when the DHCP-DDNS application command method
has been invoked.
% DHCP_DDNS_CONFIGURE configuration update received: %1
% DHCP_DDNS_CONFIGURE configuration %1 received: %2
This is a debug message issued when the DHCP-DDNS application configure method
has been invoked.
......
......@@ -192,17 +192,18 @@ D2Process::shutdown(isc::data::ConstElementPtr args) {
isc::data::ConstElementPtr
D2Process::configure(isc::data::ConstElementPtr config_set, bool check_only) {
LOG_DEBUG(d2_logger, DBGLVL_TRACE_BASIC,
DHCP_DDNS_CONFIGURE).arg(config_set->str());
LOG_DEBUG(d2_logger, DBGLVL_TRACE_BASIC, DHCP_DDNS_CONFIGURE)
.arg(check_only ? "check" : "update")
.arg(config_set->str());
/// @todo: Implement this eventually.
isc::data::ConstElementPtr answer;
answer = getCfgMgr()->parseConfig(config_set, check_only);;
if (check_only) {
return (isc::config::createAnswer(0, "Configuration check is not supported by D2."));
return (answer);
}
int rcode = 0;
isc::data::ConstElementPtr comment;
isc::data::ConstElementPtr answer = getCfgMgr()->parseConfig(config_set);;
comment = isc::config::parseAnswer(rcode, answer);
if (rcode) {
......
......@@ -158,7 +158,8 @@ public:
/// of an integer status value (0 means successful, non-zero means failure),
/// and a string explanation of the outcome.
virtual isc::data::ConstElementPtr
configure(isc::data::ConstElementPtr config_set, bool check_only);
configure(isc::data::ConstElementPtr config_set,
bool check_only = false);
/// @brief Processes the given command.
///
......
......@@ -2,7 +2,7 @@
"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
[<!ENTITY mdash "&#8212;">]>
<!--
- Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
- Copyright (C) 2013-2017 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
......@@ -52,8 +52,8 @@
<arg><option>-V</option></arg>
<arg><option>-W</option></arg>
<arg><option>-d</option></arg>
<!-- not yet <arg><option>-t</option></arg> -->
<arg><option>-c</option></arg>
<arg><option>-c<replaceable class="parameter">config-file</replaceable></option></arg>
<arg><option>-t<replaceable class="parameter">config-file</replaceable></option></arg>
</cmdsynopsis>
</refsynopsisdiv>
......@@ -105,21 +105,21 @@
</para></listitem>
</varlistentry>
<!-- not yet
<varlistentry>
<term><option>-t</option></term>
<term><option>-c</option></term>
<listitem><para>
Check the syntax of the configuration file and report the first
error if any.
Configuration file including the configuration for DHCP-DDNS server.
It may also contain configuration entries for other Kea services.
</para></listitem>
</varlistentry>
-->
<varlistentry>
<term><option>-c</option></term>
<term><option>-t</option></term>
<listitem><para>
Configuration file including the configuration for DHCP-DDNS server.
It may also contain configuration entries for other Kea services.
Check the syntax of the configuration file and report the
first error if any. Note that not all parameters are
completely checked, in particular, service socket is
not opened.
</para></listitem>
</varlistentry>
......
// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2013-2017 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
......@@ -34,9 +34,18 @@ int main(int argc, char* argv[]) {
// 'false' value disables test mode.
controller->launch(argc, argv, false);
} catch (const VersionMessage& ex) {
std::cout << ex.what() << std::endl;
std::string msg(ex.what());
if (!msg.empty()) {
std::cout << msg << std::endl;
}
} catch (const InvalidUsage& ex) {
std::string msg(ex.what());
if (!msg.empty()) {
std::cerr << msg << std::endl;
}
ret = EXIT_FAILURE;
} catch (const isc::Exception& ex) {
std::cerr << "Service failed:" << ex.what() << std::endl;
std::cerr << "Service failed: " << ex.what() << std::endl;
ret = EXIT_FAILURE;
}
......
......@@ -168,7 +168,8 @@ public:
// The JSON parsed ok and we've added the defaults, pass the config
// into the Element parser and check for the expected outcome.
data::ConstElementPtr answer = cfg_mgr_->parseConfig(config_set_);
data::ConstElementPtr answer;
answer = cfg_mgr_->parseConfig(config_set_, false);
// Extract the result and error text from the answer.
int rcode = 0;
......@@ -601,7 +602,7 @@ TEST_F(D2CfgMgrTest, fullConfig) {
// Verify that parsing the exact same configuration a second time
// does not cause a duplicate value errors.
answer_ = cfg_mgr_->parseConfig(config_set_);
answer_ = cfg_mgr_->parseConfig(config_set_, false);
ASSERT_TRUE(checkAnswer(0));
}
......
......@@ -181,12 +181,22 @@ TEST_F(D2ControllerTest, configUpdateTests) {
isc::config::parseAnswer(rcode, answer);
EXPECT_EQ(0, rcode);
// Verify that given a valid config we get a successful check result.
answer = checkConfig(config_set);
isc::config::parseAnswer(rcode, answer);
EXPECT_EQ(0, rcode);
// Use an invalid configuration to verify parsing error return.
std::string config = "{ \"bogus\": 1000 } ";
config_set = isc::data::Element::fromJSON(config);
answer = updateConfig(config_set);
isc::config::parseAnswer(rcode, answer);
EXPECT_EQ(1, rcode);
// Use an invalid configuration to verify checking error return.
answer = checkConfig(config_set);
isc::config::parseAnswer(rcode, answer);
EXPECT_EQ(1, rcode);
}
/// @brief Command execution tests.
......
# Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
# Copyright (C) 2014-2017 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
......@@ -36,7 +36,59 @@ CONFIG="{
}
}"
# Invalid configuration (invalid port) to check that D2
# Invalid configuration (syntax error) to check that Kea can check syntax.
CONFIG_BAD_SYNTAX="{
\"DhcpDdns\":
{
\"ip-address\": \"127.0.0.1\",
\"port\": BOGUS,
\"tsig-keys\": [],
\"forward-ddns\" : {},
\"reverse-ddns\" : {}
},
\"Logging\":
{
\"loggers\": [
{
\"name\": \"kea-dhcp-ddns\",
\"output_options\": [
{
\"output\": \"$LOG_FILE\"
}
],
\"severity\": \"INFO\"
}
]
}
}"
# Invalid configuration (out of range port) to check that Kea can check syntax.
CONFIG_BAD_VALUE="{
\"DhcpDdns\":
{
\"ip-address\": \"127.0.0.1\",
\"port\": 80000,
\"tsig-keys\": [],
\"forward-ddns\" : {},
\"reverse-ddns\" : {}
},
\"Logging\":
{
\"loggers\": [
{
\"name\": \"kea-dhcp-ddns\",
\"output_options\": [
{
\"output\": \"$LOG_FILE\"
}
],
\"severity\": \"INFO\"
}
]
}
}"
# Invalid value configuration (invalid port) to check that D2
# gracefully handles reconfiguration errors.
CONFIG_INVALID="{
\"DhcpDdns\":
......@@ -70,6 +122,33 @@ bin_path=@abs_top_builddir@/src/bin/d2
# Import common test library.
. @abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh
# This test verifies that syntax checking works properly. This function
# requires 3 parameters:
# testname
# config - string with a content of the config (will be written to a file)
# exp_code - expected exit code returned by kea (0 - success, 1 - failure)
syntax_check_test() {
local TESTNAME="${1}"
local CONFIG="${2}"
local EXP_CODE="${3}"
# Log the start of the test and print test name.
test_start $TESTNAME
# Remove dangling Kea instances and remove log files.
cleanup
# Create correct configuration file.
create_config "${CONFIG}"
# Check it
printf "Running command %s.\n" "\"${bin_path}/${bin} -t ${CFG_FILE}\""
${bin_path}/${bin} -t ${CFG_FILE}
exit_code=$?
if [ ${exit_code} -ne $EXP_CODE ]; then
printf "ERROR: expected exit code ${EXP_CODE}, got ${exit_code}\n"
clean_exit 1
fi
test_finish 0
}
# This test verifies that D2 can be reconfigured with a SIGHUP signal.
dynamic_reconfiguration_test() {
# Log the start of the test and print test name.
......@@ -233,3 +312,6 @@ shutdown_test "dhcp-ddns.sigterm_test" 15
shutdown_test "dhcp-ddns.sigint_test" 2
version_test "dhcp-ddns.version"
logger_vars_test "dhcp-ddns.variables"
syntax_check_test "dhcp-ddns.syntax_check_success" "${CONFIG}" 0
syntax_check_test "dhcp-ddns.syntax_check_bad_syntax" "${CONFIG_BAD_SYNTAX}" 1
syntax_check_test "dhcp-ddns.syntax_check_bad_values" "${CONFIG_BAD_VALUE}" 1
......@@ -152,7 +152,7 @@ public:
// try DHCPDDNS configure
ConstElementPtr status;
try {
status = srv_->parseConfig(d2);
status = srv_->parseConfig(d2, false);
} catch (const std::exception& ex) {
ADD_FAILURE() << "configure for " << operation
<< " failed with " << ex.what()
......
......@@ -122,7 +122,8 @@ DCfgMgrBase::setContext(DCfgContextBasePtr& context) {
}
isc::data::ConstElementPtr
DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set,
bool check_only) {
LOG_DEBUG(dctl_logger, DBGLVL_COMMAND,
DCTL_CONFIG_START).arg(config_set->str());
......@@ -245,9 +246,15 @@ DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
}
// Everything was fine. Configuration set processed successfully.
LOG_INFO(dctl_logger, DCTL_CONFIG_COMPLETE).arg(getConfigSummary(0));
answer = isc::config::createAnswer(0, "Configuration committed.");
if (!check_only) {
LOG_INFO(dctl_logger, DCTL_CONFIG_COMPLETE).arg(getConfigSummary(0));
answer = isc::config::createAnswer(0, "Configuration committed.");
} else {
answer = isc::config::createAnswer(0, "Configuration seems sane.");
LOG_INFO(dctl_logger, DCTL_CONFIG_CHECK_COMPLETE)
.arg(getConfigSummary(0))
.arg(config::answerToText(answer));
}
} catch (const std::exception& ex) {
LOG_ERROR(dctl_logger, DCTL_PARSER_FAIL).arg(ex.what());
answer = isc::config::createAnswer(1, ex.what());
......@@ -257,6 +264,12 @@ DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
return (answer);
}
if (check_only) {
// If this is a configuration check only, then don't actually apply
// the configuration and reverse to the previous one.
context_ = original_context;
}
return (answer);
}
......
......@@ -246,7 +246,7 @@ typedef std::vector<std::string> ElementIdList;
/// update context with parsed results
/// break on error
///
/// if an error occurred
/// if an error occurred or this is only a check
/// restore configuration context from backup
/// @endcode
///
......@@ -281,7 +281,7 @@ typedef std::vector<std::string> ElementIdList;
/// 1. implementation calls simpleParseConfig from its configure method.
/// 2. simpleParseConfig makes a configuration context
/// 3. parse method from the derived class is called
/// 4. if the configuration was unsuccessful of this is only a check, the
/// 4. if the configuration was unsuccessful or this is only a check, the
/// old context is reinstantiated. If not, the configuration is kept.
///
/// See @ref isc::agent::CtrlAgentCfgMgr and @ref isc::agent::CtrlAgentProcess
......@@ -303,12 +303,14 @@ public:
/// the parsing as described in the class brief.
///
/// @param config_set is a set of configuration elements to be parsed.
/// @param check_only true if the config is to be checked only, but not applied
///
/// @return an Element that contains the results of configuration composed
/// of an integer status value (0 means successful, non-zero means failure),
/// and a string explanation of the outcome.
isc::data::ConstElementPtr parseConfig(isc::data::ConstElementPtr
config_set);
isc::data::ConstElementPtr
parseConfig(isc::data::ConstElementPtr config_set,
bool check_only = false);
/// @brief Acts as the receiver of new configurations.
......
......@@ -37,7 +37,7 @@ DControllerBasePtr DControllerBase::controller_;
// Note that the constructor instantiates the controller's primary IOService.
DControllerBase::DControllerBase(const char* app_name, const char* bin_name)
: app_name_(app_name), bin_name_(bin_name),
verbose_(false), spec_file_name_(""),
verbose_(false), check_only_(false), spec_file_name_(""),
io_service_(new isc::asiolink::IOService()),
io_signal_queue_() {
}
......@@ -68,11 +68,17 @@ DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
parseArgs(argc, argv);
} catch (const InvalidUsage& ex) {
usage(ex.what());
throw; // rethrow it
// rethrow it with an empty message
isc_throw(InvalidUsage, "");
}
setProcName(bin_name_);
if (isCheckOnly()) {
checkConfigOnly();
return;
}
// It is important that we set a default logger name because this name
// will be used when the user doesn't provide the logging configuration
// in the Kea configuration file.
......@@ -146,16 +152,71 @@ DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
.arg(app_name_).arg(getpid()).arg(VERSION);
}
void
DControllerBase::checkConfigOnly() {
try {
// We need to initialize logging, in case any error
// messages are to be printed.
// This is just a test, so we don't care about lockfile.
setenv("KEA_LOCKFILE_DIR", "none", 0);
isc::dhcp::CfgMgr::instance().setDefaultLoggerName(bin_name_);
isc::dhcp::CfgMgr::instance().setVerbose(verbose_);
Daemon::loggerInit(bin_name_.c_str(), verbose_);
// Check the syntax first.
std::string config_file = getConfigFile();
if (config_file.empty()) {
// Basic sanity check: file name must not be empty.
isc_throw(InvalidUsage, "JSON configuration file not specified");
}
isc::data::ConstElementPtr whole_config = parseFile(config_file);
if (!whole_config) {
// No fallback to fromJSONFile
isc_throw(InvalidUsage, "No configuration found");
}
if (verbose_) {
std::cerr << "Syntax check OK" << std::endl;
}
// Check the logic next.
isc::data::ConstElementPtr module_config;
module_config = whole_config->get(getAppName());
if (!module_config) {
isc_throw(InvalidUsage, "Config file " << config_file <<
" does not include '" << getAppName() << "' entry");
}
// Get an application process object.
initProcess();
isc::data::ConstElementPtr answer;
answer = checkConfig(module_config);
int rcode = 0;
answer = isc::config::parseAnswer(rcode, answer);
if (rcode != 0) {
isc_throw(InvalidUsage, "Error encountered: "
<< answer->stringValue());
}
} catch (const VersionMessage&) {
throw;
} catch (const InvalidUsage&) {
throw;
} catch (const std::exception& ex) {
isc_throw(InvalidUsage, "Syntax check failed with: " << ex.what());
}
return;
}
void
DControllerBase::parseArgs(int argc, char* argv[])
{
// Iterate over the given command line options. If its a stock option
// ("s" or "v") handle it here. If its a valid custom option, then
// ("c" or "d") handle it here. If its a valid custom option, then
// invoke customOption.
int ch;
opterr = 0;
optind = 1;
std::string opts("dvVWc:" + getCustomOpts());
std::string opts("dvVWc:t:" + getCustomOpts());
while ((ch = getopt(argc, argv, opts.c_str())) != -1) {
switch (ch) {
case 'd':
......@@ -182,12 +243,17 @@ DControllerBase::parseArgs(int argc, char* argv[])
break;
case 'c':
case 't':
// config file name
if (optarg == NULL) {
isc_throw(InvalidUsage, "configuration file name missing");
}
setConfigFile(optarg);
if (ch == 't') {
check_only_ = true;
}
break;
case '?': {
......@@ -333,6 +399,12 @@ DControllerBase::updateConfig(isc::data::ConstElementPtr new_config) {
return (process_->configure(new_config, false));
}
// Instance method for checking new config
isc::data::ConstElementPtr
DControllerBase::checkConfig(isc::data::ConstElementPtr new_config) {
return (process_->configure(new_config, true));
}
// Instance method for executing commands