Commit 30023d9a authored by Francis Dupont's avatar Francis Dupont
Browse files

[master] Merged (rebased) trac3770 (command line test config)

parents 7c08bdc3 d821390c
......@@ -42,6 +42,18 @@
ports other than the standard ones will not be able to
handle regular DHCPv4 queries.</simpara>
</listitem>
<listitem>
<simpara>
<command>-t <replaceable>file</replaceable></command> -
specifies the configuration file to be tested. Kea-dhcp4
will attempt to load it, and will conduct sanity
checks. Note that certain checks are possible only while
running the actual server. The actual status is reported
with exit code (0 = configuration looks ok, 1 = error
encountered). Kea will print out log messages to standard
output and error to standard error when testing
configuration.</simpara>
</listitem>
<listitem>
<simpara>
<command>-v</command> - prints out the Kea version and exits.
......
......@@ -42,6 +42,18 @@
ports other than the standard ones will not be able to
handle regular DHCPv6 queries.</simpara>
</listitem>
<listitem>
<simpara>
<command>-t <replaceable>file</replaceable></command> -
specifies the configuration file to be tested. Kea-dhcp6
will attempt to load it, and will conduct sanity
checks. Note that certain checks are possible only while
running the actual server. The actual status is reported
with exit code (0 = configuration looks ok, 1 = error
encountered). Kea will print out log messages to standard
output and error to standard error when testing
configuration.</simpara>
</listitem>
<listitem>
<simpara>
<command>-v</command> - prints out the Kea version and exits.
......
......@@ -52,7 +52,8 @@
<arg><option>-V</option></arg>
<arg><option>-W</option></arg>
<arg><option>-d</option></arg>
<arg><option>-s</option></arg>
<!-- not yet <arg><option>-t</option></arg> -->
<arg><option>-c</option></arg>
</cmdsynopsis>
</refsynopsisdiv>
......@@ -104,6 +105,16 @@
</para></listitem>
</varlistentry>
<!-- not yet
<varlistentry>
<term><option>-t</option></term>
<listitem><para>
Check the syntax of the configuration file and report the first
error if any.
</para></listitem>
</varlistentry>
-->
<varlistentry>
<term><option>-c</option></term>
<listitem><para>
......
......@@ -406,7 +406,8 @@ void configureCommandChannel() {
}
isc::data::ConstElementPtr
configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set,
bool check_only) {
if (!config_set) {
ConstElementPtr answer = isc::config::createAnswer(1,
string("Can't parse NULL config"));
......@@ -583,9 +584,6 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
<< " (" << config_pair.second->getPosition() << ")");
}
// Setup the command channel.
configureCommandChannel();
// Apply global options in the staging config.
Dhcp4ConfigParser global_parser;
global_parser.parse(srv_cfg, mutable_cfg);
......@@ -608,12 +606,25 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
rollback = true;
}
if (check_only) {
rollback = true;
if (!answer) {
answer = isc::config::createAnswer(0,
"Configuration seems sane. Control-socket, hook-libraries, and D2 "
"configuration were sanity checked, but not applied.");
}
}
// So far so good, there was no parsing error so let's commit the
// configuration. This will add created subnets and option values into
// the server's configuration.
// This operation should be exception safe but let's make sure.
if (!rollback) {
try {
// Setup the command channel.
configureCommandChannel();
// No need to commit interface names as this is handled by the
// CfgMgr::commit() function.
......
......@@ -40,6 +40,10 @@ class Dhcpv4Srv;
/// extra parameter is a reference to DHCPv4 server component. It is currently
/// not used and CfgMgr::instance() is accessed instead.
///
/// Test-only mode added. If check_only flag is set to true, the configuration
/// is parsed, but the actual change is not applied. The goal is to have
/// the ability to test configuration.
///
/// This method does not throw. It catches all exceptions and returns them as
/// reconfiguration statuses. It may return the following response codes:
/// 0 - configuration successful
......@@ -48,10 +52,12 @@ class Dhcpv4Srv;
/// values in to server's configuration)
///
/// @param config_set a new configuration (JSON) for DHCPv4 server
/// @param check_only whether this configuration is for testing only
/// @return answer that contains result of reconfiguration
isc::data::ConstElementPtr
configureDhcp4Server(Dhcpv4Srv&,
isc::data::ConstElementPtr config_set);
isc::data::ConstElementPtr config_set,
bool check_only = false);
}; // end of isc::dhcp namespace
}; // end of isc namespace
......
......@@ -52,8 +52,9 @@
<arg><option>-V</option></arg>
<arg><option>-W</option></arg>
<arg><option>-d</option></arg>
<arg><option>-c<replaceable class="parameter">config-file</replaceable></option></arg>
<arg><option>-p<replaceable class="parameter">port-number</replaceable></option></arg>
<arg><option>-c <replaceable class="parameter">config-file</replaceable></option></arg>
<arg><option>-t <replaceable class="parameter">config-file</replaceable></option></arg>
<arg><option>-p <replaceable class="parameter">port-number</replaceable></option></arg>
</cmdsynopsis>
</refsynopsisdiv>
......@@ -109,6 +110,16 @@
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-t</option></term>
<listitem><para> Check the configuration file
and report the first error if any. Note
that not all parameters are completely checked, in
particular, service and control channel sockets
are not opened, and hook libraries are not loaded.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-p</option></term>
<listitem><para>
......
// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-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
......@@ -8,6 +8,9 @@
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/parser_context.h>
#include <dhcp4/json_config_parser.h>
#include <cc/command_interpreter.h>
#include <dhcpsrv/cfgmgr.h>
#include <log/logger_support.h>
#include <log/logger_manager.h>
......@@ -17,6 +20,7 @@
#include <iostream>
using namespace isc::data;
using namespace isc::dhcp;
using namespace std;
......@@ -38,12 +42,13 @@ usage() {
cerr << "Kea DHCPv4 server, version " << VERSION << endl;
cerr << endl;
cerr << "Usage: " << DHCP4_NAME
<< " -[v|V|W] [-d] [-c cfgfile] [-p number]" << endl;
<< " -[v|V|W] [-d] [-{c|t} cfgfile] [-p number]" << endl;
cerr << " -v: print version number and exit" << endl;
cerr << " -V: print extended version and exit" << endl;
cerr << " -W: display the configuration report and exit" << endl;
cerr << " -d: debug mode with extra verbosity (former -v)" << endl;
cerr << " -c file: specify configuration file" << endl;
cerr << " -t file: check the configuration file syntax and exit" << endl;
cerr << " -p number: specify non-standard port number 1-65535 "
<< "(useful for testing only)" << endl;
exit(EXIT_FAILURE);
......@@ -56,11 +61,12 @@ main(int argc, char* argv[]) {
int port_number = DHCP4_SERVER_PORT; // The default. any other values are
// useful for testing only.
bool verbose_mode = false; // Should server be verbose?
bool check_mode = false; // Check syntax
// The standard config file
std::string config_file("");
while ((ch = getopt(argc, argv, "dvVWc:p:")) != -1) {
while ((ch = getopt(argc, argv, "dvVWc:p:t:")) != -1) {
switch (ch) {
case 'd':
verbose_mode = true;
......@@ -78,6 +84,10 @@ main(int argc, char* argv[]) {
cout << isc::detail::getConfigReport() << endl;
return (EXIT_SUCCESS);
case 't':
check_mode = true;
// falls through
case 'c': // config file
config_file = optarg;
break;
......@@ -114,6 +124,54 @@ main(int argc, char* argv[]) {
usage();
}
if (check_mode) {
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);
CfgMgr::instance().setDefaultLoggerName(DHCP4_ROOT_LOGGER_NAME);
Daemon::loggerInit(DHCP4_ROOT_LOGGER_NAME, verbose_mode);
// Check the syntax first.
Parser4Context parser;
ConstElementPtr json;
json = parser.parseFile(config_file, Parser4Context::PARSER_DHCP4);
if (!json) {
cerr << "No configuration found" << endl;
return (EXIT_FAILURE);
}
if (verbose_mode) {
cerr << "Syntax check OK" << endl;
}
// Check the logic next.
ConstElementPtr dhcp4 = json->get("Dhcp4");
if (!dhcp4) {
cerr << "Missing mandatory Dhcp4 element" << endl;
return (EXIT_FAILURE);
}
ControlledDhcpv4Srv server(0);
ConstElementPtr answer;
// Now we pass the Dhcp4 configuration to the server, but
// tell it to check the configuration only (check_only = true)
answer = configureDhcp4Server(server, dhcp4, true);
int status_code = 0;
answer = isc::config::parseAnswer(status_code, answer);
if (status_code == 0) {
return (EXIT_SUCCESS);
} else {
cerr << "Error encountered: " << answer->stringValue() << endl;
return (EXIT_FAILURE);
}
} catch (const std::exception& ex) {
cerr << "Syntax check failed with: " << ex.what() << endl;
}
return (EXIT_FAILURE);
}
int ret = EXIT_SUCCESS;
try {
// It is important that we set a default logger name because this name
......
# 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
......@@ -57,6 +57,84 @@ CONFIG="{
]
}
}"
# Invalid configuration (syntax error) to check that Kea can check syntax.
# This config has following errors:
# - it should be interfaces-config/interfaces, not interfaces
# - it should be subnet4/pools, no subnet4/pool
CONFIG_BAD_SYNTAX="{
\"Dhcp4\":
{
\"interfaces\": [ ],
\"valid-lifetime\": 4000,
\"renew-timer\": 1000,
\"rebind-timer\": 2000,
\"lease-database\":
{
\"type\": \"memfile\",
\"persist\": false
},
\"subnet4\": [
{
\"subnet\": \"10.0.0.0/8\",
\"pool\": [ { \"pool\": \"10.0.0.10-10.0.0.100\" } ]
} ]
},
\"Logging\":
{
\"loggers\": [
{
\"name\": \"kea-dhcp4\",
\"output_options\": [
{
\"output\": \"$LOG_FILE\"
}
],
\"severity\": \"INFO\"
}
]
}
}"
# This config has bad pool values. The pool it out of scope for the subnet
# it is defined in. Syntactically the config is correct, though.
CONFIG_BAD_VALUES="{
\"Dhcp4\":
{
\"interfaces-config\": {
\"interfaces\": [ ]
},
\"valid-lifetime\": 4000,
\"renew-timer\": 1000,
\"rebind-timer\": 2000,
\"lease-database\":
{
\"type\": \"memfile\",
\"persist\": false
},
\"subnet4\": [
{
\"subnet\": \"10.0.0.0/8\",
\"pools\": [ { \"pool\": \"192.168.0.10-192.168.0.100\" } ]
} ]
},
\"Logging\":
{
\"loggers\": [
{
\"name\": \"kea-dhcp4\",
\"output_options\": [
{
\"output\": \"$LOG_FILE\"
}
],
\"severity\": \"INFO\"
}
]
}
}"
# Invalid configuration (negative valid-lifetime) to check that Kea
# gracefully handles reconfiguration errors.
CONFIG_INVALID="{
......@@ -103,6 +181,33 @@ bin_path=@abs_top_builddir@/src/bin/dhcp4
# 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 DHCPv4 can be reconfigured with a SIGHUP signal.
dynamic_reconfiguration_test() {
# Log the start of the test and print test name.
......@@ -380,3 +485,6 @@ shutdown_test "dhcpv4.sigint_test" 2
version_test "dhcpv4.version"
logger_vars_test "dhcpv4.variables"
lfc_timer_test
syntax_check_test "dhcpv4.syntax_check_success" "${CONFIG}" 0
syntax_check_test "dhcpv4.syntax_check_bad_syntax" "${CONFIG_BAD_SYNTAX}" 1
syntax_check_test "dhcpv4.syntax_check_bad_values" "${CONFIG_BAD_VALUES}" 1
......@@ -612,7 +612,9 @@ void configureCommandChannel() {
}
isc::data::ConstElementPtr
configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set,
bool check_only) {
if (!config_set) {
ConstElementPtr answer = isc::config::createAnswer(1,
string("Can't parse NULL config"));
......@@ -807,9 +809,6 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
<< " (" << config_pair.second->getPosition() << ")");
}
// Setup the command channel.
configureCommandChannel();
// Apply global options in the staging config.
Dhcp6ConfigParser global_parser;
global_parser.parse(srv_config, mutable_cfg);
......@@ -830,6 +829,15 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
rollback = true;
}
if (check_only) {
rollback = true;
if (!answer) {
answer = isc::config::createAnswer(0,
"Configuration seems sane. Control-socket, hook-libraries, and D2 "
"configuration were sanity checked, but not applied.");
}
}
// So far so good, there was no parsing error so let's commit the
// configuration. This will add created subnets and option values into
// the server's configuration.
......@@ -837,6 +845,9 @@ configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
if (!rollback) {
try {
// Setup the command channel.
configureCommandChannel();
// No need to commit interface names as this is handled by the
// CfgMgr::commit() function.
......
......@@ -24,6 +24,10 @@ class Dhcpv6Srv;
/// extra parameter is a reference to DHCPv6 server component. It is currently
/// not used and CfgMgr::instance() is accessed instead.
///
/// Test-only mode is supported. If check_only flag is set to true, the
/// configuration is parsed, but the actual change is not applied. The goal is
/// to have the ability to test configuration.
///
/// This method does not throw. It catches all exceptions and returns them as
/// reconfiguration statuses. It may return the following response codes:
/// 0 - configuration successful
......@@ -36,7 +40,8 @@ class Dhcpv6Srv;
/// @return answer that contains result of the reconfiguration.
/// @throw Dhcp6ConfigError if trying to create a parser for NULL config.
isc::data::ConstElementPtr
configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set);
configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set,
bool check_only = false);
}; // end of isc::dhcp namespace
}; // end of isc namespace
......
......@@ -52,8 +52,9 @@
<arg><option>-V</option></arg>
<arg><option>-W</option></arg>
<arg><option>-d</option></arg>
<arg><option>-c<replaceable class="parameter">config-file</replaceable></option></arg>
<arg><option>-p<replaceable class="parameter">port-number</replaceable></option></arg>
<arg><option>-c <replaceable class="parameter">config-file</replaceable></option></arg>
<arg><option>-t <replaceable class="parameter">config-file</replaceable></option></arg>
<arg><option>-p <replaceable class="parameter">port-number</replaceable></option></arg>
</cmdsynopsis>
</refsynopsisdiv>
......@@ -110,6 +111,16 @@
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-t</option></term>
<listitem><para> Check the configuration file
and report the first error if any. Note
that not all parameters are completely checked, in
particular, service and control channel sockets
are not opened, and hook libraries are not loaded.
</para></listitem>
</varlistentry>
<varlistentry>
<term><option>-p</option></term>
<listitem><para>
......
// Copyright (C) 2011-2016 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-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
......@@ -8,6 +8,8 @@
#include <dhcp6/ctrl_dhcp6_srv.h>
#include <dhcp6/dhcp6_log.h>
#include <dhcp6/parser_context.h>
#include <dhcp6/json_config_parser.h>
#include <dhcpsrv/cfgmgr.h>
#include <log/logger_support.h>
#include <log/logger_manager.h>
......@@ -18,6 +20,7 @@
#include <iostream>
using namespace isc::data;
using namespace isc::dhcp;
using namespace std;
......@@ -43,12 +46,13 @@ usage() {
cerr << "Kea DHCPv6 server, version " << VERSION << endl;
cerr << endl;
cerr << "Usage: " << DHCP6_NAME
<< " -[v|V|W] [-d] [-c cfgfile] [-p port_number]" << endl;
<< " -[v|V|W] [-d] [-{c|t} cfgfile] [-p port_number]" << endl;
cerr << " -v: print version number and exit." << endl;
cerr << " -V: print extended version and exit" << endl;
cerr << " -W: display the configuration report and exit" << endl;
cerr << " -d: debug mode with extra verbosity (former -v)" << endl;
cerr << " -c file: specify configuration file" << endl;
cerr << " -t file: check the configuration file syntax and exit" << endl;
cerr << " -p number: specify non-standard port number 1-65535 "
<< "(useful for testing only)" << endl;
exit(EXIT_FAILURE);
......@@ -61,11 +65,12 @@ main(int argc, char* argv[]) {
int port_number = DHCP6_SERVER_PORT; // The default. Any other values are
// useful for testing only.
bool verbose_mode = false; // Should server be verbose?
bool check_mode = false; // Check syntax
// The standard config file
std::string config_file("");
while ((ch = getopt(argc, argv, "dvVWc:p:")) != -1) {
while ((ch = getopt(argc, argv, "dvVWc:p:t:")) != -1) {
switch (ch) {
case 'd':
verbose_mode = true;
......@@ -83,6 +88,10 @@ main(int argc, char* argv[]) {
cout << isc::detail::getConfigReport() << endl;
return (EXIT_SUCCESS);
case 't':
check_mode = true;
// falls through
case 'c': // config file
config_file = optarg;
break;
......@@ -118,6 +127,55 @@ main(int argc, char* argv[]) {
usage();
}
if (check_mode) {
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);
CfgMgr::instance().setDefaultLoggerName(DHCP6_ROOT_LOGGER_NAME);
Daemon::loggerInit(DHCP6_ROOT_LOGGER_NAME, verbose_mode);
// Check the syntax first.
Parser6Context parser;
ConstElementPtr json;
json = parser.parseFile(config_file, Parser6Context::PARSER_DHCP6);
if (!json) {
cerr << "No configuration found" << endl;
return (EXIT_FAILURE);
}
if (verbose_mode) {
cerr << "Syntax check OK" << endl;
}
// Check the logic next.
ConstElementPtr dhcp6 = json->get("Dhcp6");
if (!dhcp6) {
cerr << "Missing mandatory Dhcp6 element" << endl;
return (EXIT_FAILURE);
}
ControlledDhcpv6Srv server(0);
ConstElementPtr answer;
// Now we pass the Dhcp6 configuration to the server, but
// tell it to check the configuration only (check_only = true)
answer = configureDhcp6Server(server, dhcp6, true);
int status_code = 0;
answer = isc::config::parseAnswer(status_code, answer);
if (status_code == 0) {
return (EXIT_SUCCESS);
} else {
cerr << "Error encountered: " << answer->stringValue() << endl;
return (EXIT_FAILURE);
}
return (EXIT_SUCCESS);
} catch (const std::exception& ex) {
cerr << "Syntax check failed with " << ex.what() << endl;
}
return (EXIT_FAILURE);
}
int ret = EXIT_SUCCESS;
try {
......
......@@ -59,12 +59,53 @@ CONFIG="{
]
}
}"
# Invalid configuration (syntax error) to check that Kea can check syntax.