Commit d60636d4 authored by Marcin Siodelski's avatar Marcin Siodelski

[#103,!277] Setup timer for CB config fetch in the DHCPv4 server.

parent c6f01c0e
// Copyright (C) 2014-2018 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2014-2019 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
......@@ -674,6 +674,32 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
return (isc::config::createAnswer(1, err.str()));
auto ctl_info = CfgMgr::instance().getStagingCfg()->getConfigControlInfo();
if (ctl_info) {
long fetch_time = static_cast<long>(ctl_info->getConfigFetchWaitTime());
// Only schedule the CB fetch timer if the fetch wait time is greater
// than 0.
if (fetch_time > 0) {
// The port number is 0 only when we run unit tests. In such case, we
// want to use milliseconds unit for the specified interval. Otherwise,
// we use seconds. Note that using milliseconds as a unit in unit tests
// prevents us from waiting 1 second on more before the timer goes off.
// Instead, we wait one millisecond which significantly reduces the
// test time.
if (server_->getServerPort() != 0) {
fetch_time = 1000 * fetch_time;
server_, CfgMgr::instance().getStagingCfg()),
// This hook point notifies hooks libraries that the configuration of the
// DHCPv4 server has completed. It provides the hook library with the pointer
// to the common IO service object, new server configuration in the JSON
......@@ -941,5 +967,25 @@ ControlledDhcpv4Srv::dbLostCallback(ReconnectCtlPtr db_reconnect_ctl) {
ControlledDhcpv4Srv::cbFetchUpdates(const SrvConfigPtr& srv_cfg) {
try {
// The true value indicates that the server should not reconnect
// to the configuration backends and should take into account
// audit entries stored in the database since last fetch.
server_->getCBControl()->databaseConfigFetch(srv_cfg, true);
} catch (const std::exception& ex) {
// Reschedule the timer to fetch new updates or re-try if
// the previous attempt resulted in an error.
if (TimerMgr::instance()->isTimerRegistered("Dhcp4CBFetchTimer")) {
}; // end of isc::dhcp namespace
}; // end of isc namespace
// Copyright (C) 2012-2018 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-2019 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
......@@ -360,6 +360,16 @@ private:
/// @return false if reconnect is not configured, true otherwise
bool dbLostCallback(db::ReconnectCtlPtr db_reconnect_ctl);
/// @brief Callback invoked periodically to fetch configuration updates
/// from the Config Backends.
/// This method calls @c CBControlDHCPv4::databaseConfigFetch and then
/// reschedules the timer.
/// @param srv_cfg Server configuration holding the database credentials
/// and server tag.
void cbFetchUpdates(const SrvConfigPtr& srv_cfg);
/// @brief Static pointer to the sole instance of the DHCP server.
/// This is required for config and command handlers to gain access to
// File created from ../../../src/bin/dhcp4/dhcp4_messages.mes on Tue Mar 19 2019 10:21
// File created from ../../../src/bin/dhcp4/dhcp4_messages.mes on Wed Mar 20 2019 11:09
#include <cstddef>
#include <log/message_types.h>
......@@ -13,6 +13,7 @@ extern const isc::log::MessageID DHCP4_BUFFER_RECEIVED = "DHCP4_BUFFER_RECEIVED"
extern const isc::log::MessageID DHCP4_BUFFER_UNPACK = "DHCP4_BUFFER_UNPACK";
extern const isc::log::MessageID DHCP4_BUFFER_WAIT_SIGNAL = "DHCP4_BUFFER_WAIT_SIGNAL";
extern const isc::log::MessageID DHCP4_CLASS_ASSIGNED = "DHCP4_CLASS_ASSIGNED";
extern const isc::log::MessageID DHCP4_CLASS_UNDEFINED = "DHCP4_CLASS_UNDEFINED";
......@@ -144,6 +145,7 @@ const char* values[] = {
"DHCP4_BUFFER_RECEIVE_FAIL", "error on attempt to receive packet: %1",
"DHCP4_BUFFER_UNPACK", "parsing buffer received from %1 to %2 over interface %3",
"DHCP4_BUFFER_WAIT_SIGNAL", "signal received while waiting for next packet, next waiting signal is %1",
"DHCP4_CB_FETCH_UPDATES_FAIL", "error on attempt to fetch configuration updates from the configuration backend(s): %1",
"DHCP4_CLASS_ASSIGNED", "%1: client packet has been assigned to the following class(es): %2",
"DHCP4_CLASS_UNCONFIGURED", "%1: client packet belongs to an unconfigured class: %2",
"DHCP4_CLASS_UNDEFINED", "required class %1 has no definition",
// File created from ../../../src/bin/dhcp4/dhcp4_messages.mes on Tue Mar 19 2019 10:21
// File created from ../../../src/bin/dhcp4/dhcp4_messages.mes on Wed Mar 20 2019 11:09
......@@ -14,6 +14,7 @@ extern const isc::log::MessageID DHCP4_BUFFER_RECEIVED;
extern const isc::log::MessageID DHCP4_BUFFER_RECEIVE_FAIL;
extern const isc::log::MessageID DHCP4_BUFFER_UNPACK;
extern const isc::log::MessageID DHCP4_BUFFER_WAIT_SIGNAL;
extern const isc::log::MessageID DHCP4_CB_FETCH_UPDATES_FAIL;
extern const isc::log::MessageID DHCP4_CLASS_ASSIGNED;
extern const isc::log::MessageID DHCP4_CLASS_UNCONFIGURED;
extern const isc::log::MessageID DHCP4_CLASS_UNDEFINED;
......@@ -46,6 +46,13 @@ by the process. The signal will be handled before the server starts
waiting for next packets. The argument specifies the next signal to
be handled by the server.
% DHCP4_CB_FETCH_UPDATES_FAIL error on attempt to fetch configuration updates from the configuration backend(s): %1
This error message is issued when the server attempted to fetch
configuration updates from the database and this attempt failed.
The server will re-try according to the configured value of the
config-fetch-wait-time parameter. The sole argument contains the
reason for failure.
% DHCP4_CLASS_ASSIGNED %1: client packet has been assigned to the following class(es): %2
This debug message informs that incoming packet has been assigned to specified
class or classes. This is a normal behavior and indicates successful operation.
......@@ -16,9 +16,11 @@
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <dhcp4/parser_context.h>
#include <dhcp4/tests/dhcp4_test_utils.h>
#include <dhcpsrv/cb_ctl_dhcp4.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/lease.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <process/config_base.h>
#include <mysql/testutils/mysql_schema.h>
......@@ -27,9 +29,11 @@
#include <log/logger_support.h>
#include <util/stopwatch.h>
#include <boost/pointer_cast.hpp>
#include <boost/scoped_ptr.hpp>
#include <gtest/gtest.h>
#include <functional>
#include <fstream>
#include <iostream>
#include <signal.h>
......@@ -54,10 +58,84 @@ using namespace isc::hooks;
namespace {
/// @brief Test implementation of the @c CBControlDHCPv4.
/// This implementation is installed on the test server instance. It
/// overrides the implementation of the @c databaseConfigFetch function
/// to verify arguments passed to this function and throw an exception
/// when desired in the negative test scenarios. It doesn't do the
/// actual configuration fetch as this is tested elswhere and would
/// require setting up a database configuration backend.
class TestCBControlDHCPv4 : public CBControlDHCPv4 {
/// @brief Constructor.
: CBControlDHCPv4(), db_config_fetch_calls_(0), enable_throw_(false) {
/// @brief Stub implementation of the "fetch" function.
/// It checks if the @c fetch_updates_only is set to true when it
/// is a later than first invocation of the function. It also
/// throws an exception when desired by a test, to verify that the
/// server gracefully handles such exception.
/// @param fetch_updates_only Boolean flag indicating that the function
/// is executed to fetch configuration updates from the database. It
/// should be set to false when the server is starting up.
/// @throw Unexpected when configured to do so.
virtual void databaseConfigFetch(const process::ConfigPtr&,
const bool fetch_updates_only) {
if ((db_config_fetch_calls_++ > 0) && !fetch_updates_only) {
ADD_FAILURE() << "databaseConfigFetch was called with the value "
"of fetch_updates_only=false";
if (enable_throw_) {
isc_throw(Unexpected, "testing if exceptions are corectly handled");
/// @brief Returns number of invocations of the @c databaseConfigFetch.
size_t getDatabaseConfigFetchCalls() const {
return (db_config_fetch_calls_);
/// @brief Enables the object to throw from @c databaseConfigFetch.
void enableThrow() {
enable_throw_ = true;
/// @brief Counter holding number of invocations of the @c databaseConfigFetch.
size_t db_config_fetch_calls_;
/// @brief Boolean flag indicating if the @c databaseConfigFetch should
/// throw.
bool enable_throw_;
/// @brief Shared pointer to the @c TestCBControlDHCPv4.
typedef boost::shared_ptr<TestCBControlDHCPv4> TestCBControlDHCPv4Ptr;
/// @brief "Naked" DHCPv4 server.
/// Exposes internal fields and installs stub implementation of the
/// @c CBControlDHCPv4 object.
class NakedControlledDhcpv4Srv: public ControlledDhcpv4Srv {
// "Naked" DHCPv4 server, exposes internal fields
NakedControlledDhcpv4Srv():ControlledDhcpv4Srv(0) { }
/// @brief Constructor.
: ControlledDhcpv4Srv(0) {
// We're replacing the @c CBControlDHCPv4 instance with our
// stub implementation used in tests.
cb_control_.reset(new TestCBControlDHCPv4());
using ControlledDhcpv4Srv::signal_handler_;
......@@ -98,15 +176,90 @@ public:
/// @param io_service Pointer to the IO service to be ran.
/// @param timeout_ms Amount of time after which the method returns.
void runTimersWithTimeout(const IOServicePtr& io_service, const long timeout_ms) {
/// @param cond Pointer to the function which if returns true it
/// stops the IO service and causes the function to return.
void runTimersWithTimeout(const IOServicePtr& io_service, const long timeout_ms,
std::function<bool()> cond = std::function<bool()>()) {
IntervalTimer timer(*io_service);
timer.setup([&io_service]() {
bool stopped = false;
timer.setup([&io_service, &stopped]() {
stopped = true;
}, timeout_ms, IntervalTimer::ONE_SHOT);
// Run as long as the timeout hasn't occurred and the interrupting
// condition is not specified or not met.
while (!stopped && (!cond || !cond())) {
/// @brief This test verifies that the timer used to fetch the configuration
/// updates from the database works as expected.
void testConfigBackendTimer(const int config_wait_fetch_time,
const bool throw_during_fetch = false) {
std::ostringstream config;
config <<
"{ \"Dhcp4\": {"
"\"interfaces-config\": {"
" \"interfaces\": [ ]"
"\"lease-database\": {"
" \"type\": \"memfile\","
" \"persist\": false"
"\"config-control\": {"
" \"config-fetch-wait-time\": " << config_wait_fetch_time <<
"\"rebind-timer\": 2000, "
"\"renew-timer\": 1000, \n"
"\"subnet4\": [ ],"
"\"valid-lifetime\": 4000 }"
writeFile(TEST_FILE, config.str());
// Create an instance of the server and initialize it.
boost::scoped_ptr<NakedControlledDhcpv4Srv> srv;
ASSERT_NO_THROW(srv.reset(new NakedControlledDhcpv4Srv()));
// Get the CBControlDHCPv4 object belonging to this server.
auto cb_control = boost::dynamic_pointer_cast<TestCBControlDHCPv4>(srv->getCBControl());
// Instruct our stub implementation of the CBControlDHCPv4 to throw as a
// result of fetch if desired.
if (throw_during_fetch) {
// So far there should be exactly one attempt to fetch the configuration
// from the backend. That's the attempt made upon startup.
EXPECT_EQ(1, cb_control->getDatabaseConfigFetchCalls());
if (config_wait_fetch_time > 0) {
// If we're configured to run the timer, we expect that it was
// invoked at least 3 times. This is sufficient to verify that
// the timer was scheduled and that the timer continued to run
// even when an exception occurred during fetch (that's why it
// is 3 not 2).
ASSERT_NO_THROW(runTimersWithTimeout(srv->getIOService(), 500,
[cb_control]() {
// Interrupt the timers poll if we have recorded at
// least 3 attempts to fetch the updates.
return (cb_control->getDatabaseConfigFetchCalls() >= 3);
EXPECT_GE(cb_control->getDatabaseConfigFetchCalls(), 3);
} else {
// If the server is not configured to schedule the timer,
// we should still have one fetch attempt recorded.
ASSERT_NO_THROW(runTimersWithTimeout(srv->getIOService(), 500));
EXPECT_EQ(1, cb_control->getDatabaseConfigFetchCalls());
/// Name of a config file used during tests
static const char* TEST_FILE;
static const char* TEST_INCLUDE;
......@@ -648,6 +801,28 @@ TEST_F(JSONFileBackendTest, defaultLeaseDbBackend) {
// This test verifies that the timer triggering configuration updates
// is invoked according to the configured value of the
// config-fetch-wait-time.
TEST_F(JSONFileBackendTest, configBackendTimer) {
// This test verifies that the timer for triggering configuration updates
// is not invoked when the value of the config-fetch-wait-time is set
// to 0.
TEST_F(JSONFileBackendTest, configBackendTimerDisabled) {
// This test verifies that the server will gracefully handle exceptions
// thrown from the CBControlDHCPv4::databaseConfigFetch, i.e. will
// reschedule the timer.
TEST_F(JSONFileBackendTest, configBackendTimerWithThrow) {
// The true value instructs the test to throw during the fetch.
testConfigBackendTimer(1, true);
// Starting tests which require MySQL backend availability. Those tests
// will not be executed if Kea has been compiled without the
// --with-mysql.
......@@ -158,8 +158,8 @@ public:
/// @param fetch_updates_only boolean value indicating if the method is
/// called upon the server start up (false) or it is called to fetch
/// configuration updates (true).
void databaseConfigFetch(const ConfigPtr& srv_cfg,
const bool fetch_updates_only = false) {
virtual void databaseConfigFetch(const ConfigPtr& srv_cfg,
const bool fetch_updates_only = false) {
// If the server starts up we need to connect to the database(s).
// If there are no databases available simply do nothing.
if (!fetch_updates_only && !databaseConfigConnect(srv_cfg)) {
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment