Commit dd0270bd authored by Marcin Siodelski's avatar Marcin Siodelski
Browse files

[master] Merge branch 'trac3405'

parents e28f694d ba0df815
......@@ -56,6 +56,7 @@
* - @subpage dhcpv4DDNSIntegration
* - @subpage dhcpv4Classifier
* - @subpage dhcpv4ConfigBackend
* - @subpage dhcpv4SignalBasedReconfiguration
* - @subpage dhcpv4Other
* - @subpage dhcp6
* - @subpage dhcpv6Session
......
......@@ -134,7 +134,9 @@ bundyConfigHandler(ConstElementPtr new_config) {
void ControlledDhcpv4Srv::init(const std::string& /*config_file*/) {
void ControlledDhcpv4Srv::init(const std::string& config_file) {
// Call base class's init.
Daemon::init(config_file);
string specfile;
if (getenv("B10_FROM_BUILD")) {
......
......@@ -231,6 +231,37 @@ for ISC-DHCP. Is at least possible that similar will happen for Kea. Finally, if
extend the isc::dhcp::CfgMgr with configuration export, this approach could be used as
a migration tool.
@section dhcpv4SignalBasedReconfiguration Reconfiguring DHCPv4 server with SIGHUP signal
Online reconfiguration (reconfiguration without a need to restart the server) is an
important feature which is supported by all modern DHCP servers. When using the JSON
configuration backend, a configuration file name is specified with a command line
option of the DHCP server binary. The configuration file is used to configure the
server at startup. If the initial configuration fails, the server will fail to start.
If the server starts and configures successfully it will use the initial configuration
until it is reconfigured.
The reconfiguration request can be triggered externally (from other process) by editing
a configuration file and sending a SIGHUP signal to DHCP server process. After receiving
the SIGHUP signal, the server will re-read the configuration file specified at startup.
If the reconfiguration fails, the server will continue to run and use the last good
configuration.
The signal handler for SIGHUP (also for SIGTERM and SIGINT) are installed in the
kea_controller.cc using the @c isc::util::SignalSet class. The
@c isc::dhcp::Dhcp6Srv calls @c isc::dhcp::Daemon::handleSignal on each pass
through the main loop. This method fetches the last received signal and calls
a handler function defined in the kea_controller.cc. The handler function
calls a static function @c configure defined in the kea_controller.cc.
In order for the signal handler to know the location of the configuration file
(specified at process startup), the location of this file needs to be stored
in a static variable so as it may be directly accessed by the signal handler.
This static variable is stored in the @c dhcp::Daemon class and all Kea processes
can use it (all processes derive from this class). The configuration file
location is initialized when the @c Daemon::init method is called. Therefore,
derived classes should call it in their implementations of the @c init method.
@section dhcpv4Other Other DHCPv4 topics
For hooks API support in DHCPv4, see @ref dhcpv4Hooks.
......
......@@ -77,11 +77,6 @@ change is committed by the administrator.
A debug message indicating that the DHCPv4 server has received an
updated configuration from the BIND 10 configuration system.
% DHCP4_DB_BACKEND_STARTED lease database started (type: %1, name: %2)
This informational message is printed every time DHCPv4 server is started
and gives both the type and name of the database being used to store
lease and other information.
% DHCP4_DDNS_REQUEST_SEND_FAILED failed sending a request to b10-dhcp-ddns, error: %1, ncr: %2
This error message indicates that DHCP4 server attempted to send a DDNS
update reqeust to the DHCP-DDNS server. This is most likely a configuration or
......@@ -97,6 +92,14 @@ This error message is logged when the attempt to compute DHCID for a specified
lease has failed. The lease details and reason for failure is logged in the
message.
% DHCP4_DYNAMIC_RECONFIGURATION initate server reconfiguration using file: %1, after receiving SIGHUP signal
This is the info message logged when the DHCPv4 server starts reconfiguration
as a result of receiving SIGHUP signal.
% DHCP4_DYNAMIC_RECONFIGURATION_FAIL dynamic server reconfiguration failed with file: %1
This is an error message logged when the dynamic reconfiguration of the
DHCP server failed.
% DHCP4_EMPTY_HOSTNAME received empty hostname from the client, skipping processing of this option
This debug message is issued when the server received an empty Hostname option
from a client. Server does not process empty Hostname options and therefore
......
......@@ -82,11 +82,11 @@ namespace dhcp {
const std::string Dhcpv4Srv::VENDOR_CLASS_PREFIX("VENDOR_CLASS_");
Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast,
Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const bool use_bcast,
const bool direct_response_desired)
: shutdown_(true), alloc_engine_(), port_(port),
use_bcast_(use_bcast), hook_index_pkt4_receive_(-1),
hook_index_subnet4_select_(-1), hook_index_pkt4_send_(-1) {
: shutdown_(true), alloc_engine_(), port_(port),
use_bcast_(use_bcast), hook_index_pkt4_receive_(-1),
hook_index_subnet4_select_(-1), hook_index_pkt4_send_(-1) {
LOG_DEBUG(dhcp4_logger, DBG_DHCP4_START, DHCP4_OPEN_SOCKET).arg(port);
try {
......@@ -112,12 +112,6 @@ Dhcpv4Srv::Dhcpv4Srv(uint16_t port, const char* dbconfig, const bool use_bcast,
IfaceMgr::instance().openSockets4(port_, use_bcast_, error_handler);
}
// Instantiate LeaseMgr
LeaseMgrFactory::create(dbconfig);
LOG_INFO(dhcp4_logger, DHCP4_DB_BACKEND_STARTED)
.arg(LeaseMgrFactory::instance().getType())
.arg(LeaseMgrFactory::instance().getName());
// Instantiate allocation engine
alloc_engine_.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100,
false /* false = IPv4 */));
......@@ -175,6 +169,18 @@ Dhcpv4Srv::run() {
LOG_ERROR(dhcp4_logger, DHCP4_PACKET_RECEIVE_FAIL).arg(e.what());
}
// Handle next signal received by the process. It must be called after
// an attempt to receive a packet to properly handle server shut down.
// The SIGTERM or SIGINT will be received prior to, or during execution
// of select() (select is invoked by recivePacket()). When that happens,
// select will be interrupted. The signal handler will be invoked
// immediately after select(). The handler will set the shutdown flag
// and cause the process to terminate before the next select() function
// is called. If the function was called before receivePacket the
// process could wait up to the duration of timeout of select() to
// terminate.
handleSignal();
// Timeout may be reached or signal received, which breaks select()
// with no reception ocurred
if (!query) {
......
......@@ -83,13 +83,10 @@ public:
/// root privileges.
///
/// @param port specifies port number to listen on
/// @param dbconfig Lease manager configuration string. The default
/// of the "memfile" manager is used for testing.
/// @param use_bcast configure sockets to support broadcast messages.
/// @param direct_response_desired specifies if it is desired to
/// use direct V4 traffic.
Dhcpv4Srv(uint16_t port = DHCP4_SERVER_PORT,
const char* dbconfig = "type=memfile universe=4",
const bool use_bcast = true,
const bool direct_response_desired = true);
......
......@@ -25,11 +25,18 @@ using namespace isc::asiolink;
using namespace isc::dhcp;
using namespace std;
namespace isc {
namespace dhcp {
void
ControlledDhcpv4Srv::init(const std::string& file_name) {
namespace {
/// @brief Configure DHCPv4 server using the configuration file specified.
///
/// This function is used to both configure the DHCP server on its startup
/// and dynamically reconfigure the server when SIGHUP signal is received.
///
/// It fetches DHCPv6 server's configuration from the 'Dhcp4' section of
/// the JSON configuration file.
///
/// @param file_name Configuration file location.
void configure(const std::string& file_name) {
// This is a configuration backend implementation that reads the
// configuration from a JSON file.
......@@ -41,8 +48,8 @@ ControlledDhcpv4Srv::init(const std::string& file_name) {
try {
if (file_name.empty()) {
// Basic sanity check: file name must not be empty.
isc_throw(BadValue, "JSON configuration file not specified. Please "
"use -c command line option.");
isc_throw(isc::BadValue, "JSON configuration file not specified."
" Please use -c command line option.");
}
// Read contents of the file and parse it as JSON
......@@ -51,8 +58,8 @@ ControlledDhcpv4Srv::init(const std::string& file_name) {
if (!json) {
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
.arg("Config file " + file_name + " missing or empty.");
isc_throw(BadValue, "Unable to process JSON configuration file:"
+ file_name);
isc_throw(isc::BadValue, "Unable to process JSON configuration"
" file: " << file_name);
}
// Get Dhcp4 component from the config
......@@ -60,29 +67,30 @@ ControlledDhcpv4Srv::init(const std::string& file_name) {
if (!dhcp4) {
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
.arg("Config file " + file_name + " does not include 'Dhcp4' entry.");
isc_throw(BadValue, "Unable to process JSON configuration file:"
+ file_name);
.arg("Config file " + file_name + " does not include 'Dhcp4'"
" entry.");
isc_throw(isc::BadValue, "Unable to process JSON configuration"
" file: " << file_name);
}
// Use parsed JSON structures to configure the server
result = processCommand("config-reload", dhcp4);
result = ControlledDhcpv4Srv::processCommand("config-reload", dhcp4);
} catch (const std::exception& ex) {
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(ex.what());
isc_throw(BadValue, "Unable to process JSON configuration file:"
+ file_name);
isc_throw(isc::BadValue, "Unable to process JSON configuration file: "
<< file_name);
}
if (!result) {
// Undetermined status of the configuration. This should never happen,
// but as the configureDhcp4Server returns a pointer, it is theoretically
// possible that it will return NULL.
// but as the configureDhcp4Server returns a pointer, it is
// theoretically possible that it will return NULL.
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL)
.arg("Configuration failed: Undefined result of processCommand("
"config-reload, " + file_name + ")");
isc_throw(BadValue, "Configuration failed: Undefined result of "
"processCommand('config-reload', " + file_name + ")");
isc_throw(isc::BadValue, "Configuration failed: Undefined result of "
"processCommand('config-reload', " << file_name << ")");
}
// Now check is the returned result is successful (rcode=0) or not
......@@ -95,12 +103,64 @@ ControlledDhcpv4Srv::init(const std::string& file_name) {
reason = string(" (") + comment->stringValue() + string(")");
}
LOG_ERROR(dhcp4_logger, DHCP4_CONFIG_LOAD_FAIL).arg(reason);
isc_throw(BadValue, "Failed to apply configuration:" << reason);
isc_throw(isc::BadValue, "Failed to apply configuration: " << reason);
}
}
/// @brief Signals handler for DHCPv4 server.
///
/// This signal handler handles the following signals received by the DHCPv4
/// server process:
/// - SIGHUP - triggers server's dynamic reconfiguration.
/// - SIGTERM - triggers server's shut down.
/// - SIGINT - triggers server's shut down.
///
/// @param signo Signal number received.
void signalHandler(int signo) {
// SIGHUP signals a request to reconfigure the server.
if (signo == SIGHUP) {
// Get configuration file name.
std::string file = ControlledDhcpv4Srv::getInstance()->getConfigFile();
try {
LOG_INFO(dhcp4_logger, DHCP4_DYNAMIC_RECONFIGURATION).arg(file);
configure(file);
} catch (const std::exception& ex) {
// Log the unsuccessful reconfiguration. The reason for failure
// should be already logged. Don't rethrow an exception so as
// the server keeps working.
LOG_ERROR(dhcp4_logger, DHCP4_DYNAMIC_RECONFIGURATION_FAIL)
.arg(file);
}
} else if ((signo == SIGTERM) || (signo == SIGINT)) {
isc::data::ElementPtr params(new isc::data::MapElement());
ControlledDhcpv4Srv::processCommand("shutdown", params);
}
}
}
namespace isc {
namespace dhcp {
void
ControlledDhcpv4Srv::init(const std::string& file_name) {
// Call parent class's init to initialize file name.
Daemon::init(file_name);
// Configure the server using JSON file.
configure(file_name);
// We don't need to call openActiveSockets() or startD2() as these
// methods are called in processConfig() which is called by
// processCommand("reload-config", ...)
// Set signal handlers. When the SIGHUP is received by the process
// the server reconfiguration will be triggered. When SIGTERM or
// SIGINT will be received, the server will start shutting down.
signal_set_.reset(new isc::util::SignalSet(SIGINT, SIGHUP, SIGTERM));
// Set the pointer to the handler function.
signal_handler_ = signalHandler;
}
void ControlledDhcpv4Srv::cleanup() {
......
PYCOVERAGE_RUN = @PYCOVERAGE_RUN@
PYTESTS = dhcp4_test.py
EXTRA_DIST = $(PYTESTS)
SHTESTS =
# The test of dynamic reconfiguration based on signals will work only
# if we are using file based configuration approach.
if CONFIG_BACKEND_JSON
SHTESTS += dhcp4_reconfigure_test.sh
SHTESTS += dhcp4_sigterm_test.sh
SHTESTS += dhcp4_sigint_test.sh
endif
EXTRA_DIST = $(PYTESTS)
EXTRA_DIST += dhcp4_reconfigure_test.sh
EXTRA_DIST += dhcp4_sigterm_test.sh
EXTRA_DIST += dhcp4_sigint_test.sh
EXTRA_DIST += dhcp4_shutdown_test.sh
# Explicitly specify paths to dynamic libraries required by loadable python
# modules. That is required on Mac OS systems. Otherwise we will get exception
......@@ -21,6 +34,13 @@ check-local:
$(PYCOVERAGE_RUN) $(abs_srcdir)/$$pytest || exit ; \
done
for shtest in $(SHTESTS) ; do \
echo Running test: $$shtest ; \
export B10_LOCKFILE_DIR_FROM_BUILD=$(abs_top_builddir); \
$(abs_srcdir)/$$shtest || exit ; \
done
AM_CPPFLAGS = -I$(top_srcdir)/src/lib -I$(top_builddir)/src/lib
AM_CPPFLAGS += -I$(top_builddir)/src/bin # for generated spec_config.h header
AM_CPPFLAGS += -I$(top_srcdir)/src/bin
......@@ -33,7 +53,7 @@ AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
CLEANFILES = $(builddir)/interfaces.txt $(builddir)/logger_lockfile
CLEANFILES += $(builddir)/load_marker.txt $(builddir)/unload_marker.txt
CLEANFILES += *.json
CLEANFILES += *.json *.log
AM_CXXFLAGS = $(B10_CXXFLAGS)
if USE_CLANGPP
......@@ -121,6 +141,7 @@ dhcp4_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/dhcpsrv/testutils/libdhcpsrvtest.la
dhcp4_unittests_LDADD += $(top_builddir)/src/lib/util/io/libkea-util-io.la
endif
noinst_EXTRA_DIST = configs-list.txt
......
......@@ -36,7 +36,7 @@ public:
/// @brief Constructor
D2Dhcpv4Srv()
: Dhcpv4Srv(0, "type=memfile", false, false), error_count_(0) {
: Dhcpv4Srv(0, false, false), error_count_(0) {
}
/// @brief virtual Destructor.
......
# Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
# Test name
TEST_NAME="DHCPv4.dynamicReconfiguration"
# Path to the temporary configuration file.
CFG_FILE="test_config.json"
# Path to the Kea log file.
LOG_FILE="test.log"
# Kea configuration to be stored in the configuration file.
CONFIG="{
\"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\": [ \"10.0.0.10-10.0.0.100\" ]
} ]
}
}"
# Invalid configuration (negative valid-lifetime) to check that Kea
# gracefully handles reconfiguration errors.
CONFIG_INVALID="{
\"Dhcp4\":
{
\"interfaces\": [ ],
\"valid-lifetime\": -3,
\"renew-timer\": 1000,
\"rebind-timer\": 2000,
\"lease-database\":
{
\"type\": \"memfile\",
\"persist\": false
},
\"subnet4\": [
{
\"subnet\": \"10.0.0.0/8\",
\"pool\": [ \"10.0.0.10-10.0.0.100\" ]
} ]
}
}"
# Set the location of the executable.
BIN="b10-dhcp4"
BIN_PATH=".."
# Import common test library.
. $(dirname $0)/../../../lib/testutils/dhcp_test_lib.sh
# Log the start of the test and print test name.
test_start
# Remove dangling Kea instances and remove log files.
cleanup
# Create new configuration file.
create_config "${CONFIG}"
# Instruct Kea to log to the specific file.
set_logger
# Start Kea.
start_kea
# Wait up to 20s for Kea to start.
wait_for_kea 20
if [ ${_WAIT_FOR_KEA} -eq 0 ]; then
printf "ERROR: timeout waiting for Kea to start.\n"
clean_exit 1
fi
# Check if it is still running. It could have terminated (e.g. as a result
# of configuration failure).
get_pids
if [ ${_GET_PIDS_NUM} -ne 1 ]; then
printf "ERROR: expected one Kea process to be started. Found %d processes\
started.\n" ${_GET_PIDS_NUM}
clean_exit 1
fi
# Check in the log file, how many times server has been configured. It should
# be just once on startup.
get_reconfigs
if [ ${_GET_RECONFIGS} -ne 1 ]; then
printf "ERROR: server hasn't been configured.\n"
clean_exit 1
else
printf "Server successfully configured.\n"
fi
# Now use invalid configuration.
create_config "${CONFIG_INVALID}"
# Try to reconfigure by sending SIGHUP
send_signal 1
# The configuration should fail and the error message should be there.
wait_for_message 10 "DHCP4_CONFIG_LOAD_FAIL" 1
# After receiving SIGHUP the server should try to reconfigure itself.
# The configuration provided is invalid so it should result in
# reconfiguration failure but the server should still be running.
get_reconfigs
if [ ${_GET_RECONFIGS} -ne 1 ]; then
printf "ERROR: server has been reconfigured despite bogus configuration.\n"
clean_exit 1
elif [ ${_GET_RECONFIG_ERRORS} -ne 1 ]; then
printf "ERROR: server did not report reconfiguration error despite attempt\
to configure it with invalid configuration.\n"
clean_exit 1
fi
# Make sure the server is still operational.
get_pids
if [ ${_GET_PIDS_NUM} -ne 1 ]; then
printf "ERROR: Kea process was killed when attempting reconfiguration.\n"
clean_exit 1
fi
# Restore the good configuration.
create_config "${CONFIG}"
# Reconfigure the server with SIGHUP.
send_signal 1
# There should be two occurrences of the DHCP4_CONFIG_COMPLETE messages.
# Wait for it up to 10s.
wait_for_message 10 "DHCP4_CONFIG_COMPLETE" 2
# After receiving SIGHUP the server should get reconfigured and the
# reconfiguration should be noted in the log file. We should now
# have two configurations logged in the log file.
if [ ${_WAIT_FOR_MESSAGE} -eq 0 ]; then
printf "ERROR: server hasn't been reconfigured.\n"
clean_exit 1
else
printf "Server successfully reconfigured.\n"
fi
# Make sure the server is still operational.
get_pids
if [ ${_GET_PIDS_NUM} -ne 1 ]; then
printf "ERROR: Kea process was killed when attempting reconfiguration.\n"
clean_exit 1
fi
# All ok. Shut down Kea and exit.
clean_exit 0
# Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
# AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
# PERFORMANCE OF THIS SOFTWARE.
if [ $# -ne 2 ]; then
printf "USAGE: dhcp4_shutdown_test.sh <test_name> <signal_num>\n"
exit 1
fi
# Test name
TEST_NAME=$1
# Signal number to be used for this test.
SIG_NUM=$2
# Path to the temporary configuration file.
CFG_FILE="test_config.json"
# Path to the Kea log file.
LOG_FILE="test.log"
# Kea configuration to be stored in the configuration file.
CONFIG="{
\"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\": [ \"10.0.0.10-10.0.0.100\" ]
} ]
}
}"
# Set the location of the executable.
BIN="b10-dhcp4"
BIN_PATH=".."
# Import common test library.
. $(dirname $0)/../../../lib/testutils/dhcp_test_lib.sh
# Log the start of the test and print test name.
test_start
# Remove dangling Kea instances and remove log files.
cleanup
# Create new configuration file.
create_config "${CONFIG}"
# Instruct Kea to log to the specific file.
set_logger
# Start Kea.
start_kea
# Wait up to 20s for Kea to start.
wait_for_kea 20
if [ ${_WAIT_FOR_KEA} -eq 0 ]; then
printf "ERROR: timeout waiting for Kea to start.\n"
clean_exit 1
fi
# Check if it is still running. It could have terminated (e.g. as a result
# of configuration failure).
get_pids
if [ ${_GET_PIDS_NUM} -ne 1 ]; then
printf "ERROR: expected one Kea process to be started. Found %d processes\
started.\n" ${_GET_PIDS_NUM}
clean_exit 1
fi
# Check in the log file, how many times server has been configured. It should
# be just once on startup.
get_reconfigs
if [ ${_GET_RECONFIGS} -ne 1 ]; then
printf "ERROR: server hasn't been configured.\n"
clean_exit 1
else
printf "Server successfully configured.\n"
fi