2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-28 20:47:48 +00:00

[#104,!290] kea-dhcpv6 now supports fetching config from backends

Infrastructure has been added to kea-dhcp6 such that it can
    now be configured to fetch full and then periodic updates
    from config backends.

    Merging the actual fetched content will be done under subsequent
    issues.

src/bin/dhcp6
    ctrl_dhcp6_srv.*
        - ControlledDhcpv6Srv::processConfig() - added logic to schedule CB update timer
        - ControlledDhcpv6Srv::cbFetchUpdates() - new callback function for CB updates

    dhcp6_lexer.ll
    dhcp6_parser.yy
        -  Added config-fetch-wait-time

    dhcp6_messages.mes
        -  New log messages

    dhcp6_srv.*
        - Dhcpv6Srv::cb_control_  - new member for config backend access
        - Dhcpv6Srv::inTestMode() - new function to test for unit test mode

    json_config_parser.cc
        - configureDhcp6Server() - invokes full fetch from config backend

src/bin/dhcp6/tests
    config_backend_unittest.cc - new file/tests for config backend testing
    config_parser_unittest.cc - updated
    get_config_unittest.cc - rebuild tests
    kea_controller_unittest.cc - added CB control/timer tests

src/lib/dhcpsrv/
    dhcpsrv_messages.mes - added log message
	cb_ctl_dhcp6.* - new files that provide v6 impl of config backend controller

doc/examples/kea6/all-keys-current.json - added config-fetch-wait-time
This commit is contained in:
Thomas Markwalder 2019-03-26 15:12:06 -04:00
parent 62ad7da5f7
commit f5fe96c6ee
27 changed files with 6168 additions and 6121 deletions

View File

@ -699,7 +699,10 @@
// Type of the database, e.g. "mysql", "postgresql", "cql".
"type": "mysql"
}
]
],
// Intervals between attempts to fetch configuration updates
// via the configuration backends used.
"config-fetch-wait-time": 30
},
// Server tag.

View File

@ -695,6 +695,34 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
return (isc::config::createAnswer(1, err.str()));
}
// Setup config backend polling, if configured for it.
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) {
// When we run unit tests, 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_->inTestMode()) {
fetch_time = 1000 * fetch_time;
}
boost::shared_ptr<unsigned> failure_count(new unsigned(0));
TimerMgr::instance()->
registerTimer("Dhcp6CBFetchTimer",
boost::bind(&ControlledDhcpv6Srv::cbFetchUpdates,
server_, CfgMgr::instance().getStagingCfg(),
failure_count),
fetch_time,
asiolink::IntervalTimer::ONE_SHOT);
TimerMgr::instance()->setup("Dhcp6CBFetchTimer");
}
}
// Finally, we can commit runtime option definitions in libdhcp++. This is
// exception free.
LibDHCP::commitRuntimeOptionDefs();
@ -962,5 +990,36 @@ ControlledDhcpv6Srv::dbLostCallback(ReconnectCtlPtr db_reconnect_ctl) {
return(true);
}
void
ControlledDhcpv6Srv::cbFetchUpdates(const SrvConfigPtr& srv_cfg,
boost::shared_ptr<unsigned> failure_count) {
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,
CBControlDHCPv6::FetchMode::FETCH_UPDATE);
(*failure_count) = 0;
} catch (const std::exception& ex) {
LOG_ERROR(dhcp6_logger, DHCP6_CB_FETCH_UPDATES_FAIL)
.arg(ex.what());
// We allow at most 10 consecutive failures after which we stop
// making further attempts to fetch the configuration updates.
// Let's return without re-scheduling the timer.
if (++(*failure_count) > 10) {
LOG_ERROR(dhcp6_logger, DHCP6_CB_FETCH_UPDATES_RETRIES_EXHAUSTED);
return;
}
}
// Reschedule the timer to fetch new updates or re-try if
// the previous attempt resulted in an error.
if (TimerMgr::instance()->isTimerRegistered("Dhcp6CBFetchTimer")) {
TimerMgr::instance()->setup("Dhcp6CBFetchTimer");
}
}
}; // end of isc::dhcp namespace
}; // end of isc namespace

View File

@ -1,4 +1,4 @@
// 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
@ -357,6 +357,20 @@ private:
/// configured reconnect parameters
bool dbLostCallback(db::ReconnectCtlPtr db_reconnect_ctl);
/// @brief Callback invoked periodically to fetch configuration updates
/// from the Config Backends.
///
/// This method calls @c CBControlDHCPv6::databaseConfigFetch and then
/// reschedules the timer.
///
/// @param srv_cfg Server configuration holding the database credentials
/// and server tag.
/// @param failure_count pointer to failure counter which causes this
/// callback to stop scheduling the timer after 10 consecutive failures
/// to fetch the updates.
void cbFetchUpdates(const SrvConfigPtr& srv_cfg,
boost::shared_ptr<unsigned> failure_count);
/// @brief Static pointer to the sole instance of the DHCP server.
///
/// This is required for config and command handlers to gain access to

File diff suppressed because it is too large Load Diff

View File

@ -531,6 +531,14 @@ ControlCharacterFill [^"\\]|\\{JSONEscapeSequence}
}
}
\"config-fetch-wait-time\" {
switch(driver.ctx_) {
case isc::dhcp::Parser6Context::CONFIG_CONTROL:
return isc::dhcp::Dhcp6Parser::make_CONFIG_FETCH_WAIT_TIME(driver.loc_);
default:
return isc::dhcp::Dhcp6Parser::make_STRING("config-fetch-wait-time", driver.loc_);
}
}
\"readonly\" {
switch(driver.ctx_) {

View File

@ -1,4 +1,4 @@
// File created from ../../../src/bin/dhcp6/dhcp6_messages.mes on Fri Feb 08 2019 20:33
// File created from ../../../src/bin/dhcp6/dhcp6_messages.mes on Tue Mar 26 2019 13:41
#include <cstddef>
#include <log/message_types.h>
@ -14,6 +14,8 @@ extern const isc::log::MessageID DHCP6_ALREADY_RUNNING = "DHCP6_ALREADY_RUNNING"
extern const isc::log::MessageID DHCP6_BUFFER_RECEIVED = "DHCP6_BUFFER_RECEIVED";
extern const isc::log::MessageID DHCP6_BUFFER_UNPACK = "DHCP6_BUFFER_UNPACK";
extern const isc::log::MessageID DHCP6_BUFFER_WAIT_SIGNAL = "DHCP6_BUFFER_WAIT_SIGNAL";
extern const isc::log::MessageID DHCP6_CB_FETCH_UPDATES_FAIL = "DHCP6_CB_FETCH_UPDATES_FAIL";
extern const isc::log::MessageID DHCP6_CB_FETCH_UPDATES_RETRIES_EXHAUSTED = "DHCP6_CB_FETCH_UPDATES_RETRIES_EXHAUSTED";
extern const isc::log::MessageID DHCP6_CLASS_ASSIGNED = "DHCP6_CLASS_ASSIGNED";
extern const isc::log::MessageID DHCP6_CLASS_UNCONFIGURED = "DHCP6_CLASS_UNCONFIGURED";
extern const isc::log::MessageID DHCP6_CLASS_UNDEFINED = "DHCP6_CLASS_UNDEFINED";
@ -152,6 +154,8 @@ const char* values[] = {
"DHCP6_BUFFER_RECEIVED", "received buffer from %1:%2 to %3:%4 over interface %5",
"DHCP6_BUFFER_UNPACK", "parsing buffer received from %1 to %2 over interface %3",
"DHCP6_BUFFER_WAIT_SIGNAL", "signal received while waiting for next packet, next waiting signal is %1",
"DHCP6_CB_FETCH_UPDATES_FAIL", "error on attempt to fetch configuration updates from the configuration backend(s): %1",
"DHCP6_CB_FETCH_UPDATES_RETRIES_EXHAUSTED", "maximum number of configuration fetch attempts: 10, has been exhausted without success",
"DHCP6_CLASS_ASSIGNED", "%1: client packet has been assigned to the following class(es): %2",
"DHCP6_CLASS_UNCONFIGURED", "%1: client packet belongs to an unconfigured class: %2",
"DHCP6_CLASS_UNDEFINED", "required class %1 has no definition",

View File

@ -1,4 +1,4 @@
// File created from ../../../src/bin/dhcp6/dhcp6_messages.mes on Fri Feb 08 2019 20:33
// File created from ../../../src/bin/dhcp6/dhcp6_messages.mes on Tue Mar 26 2019 13:41
#ifndef DHCP6_MESSAGES_H
#define DHCP6_MESSAGES_H
@ -15,6 +15,8 @@ extern const isc::log::MessageID DHCP6_ALREADY_RUNNING;
extern const isc::log::MessageID DHCP6_BUFFER_RECEIVED;
extern const isc::log::MessageID DHCP6_BUFFER_UNPACK;
extern const isc::log::MessageID DHCP6_BUFFER_WAIT_SIGNAL;
extern const isc::log::MessageID DHCP6_CB_FETCH_UPDATES_FAIL;
extern const isc::log::MessageID DHCP6_CB_FETCH_UPDATES_RETRIES_EXHAUSTED;
extern const isc::log::MessageID DHCP6_CLASS_ASSIGNED;
extern const isc::log::MessageID DHCP6_CLASS_UNCONFIGURED;
extern const isc::log::MessageID DHCP6_CLASS_UNDEFINED;

View File

@ -74,6 +74,20 @@ has no definition.
This debug message informs that a class was listed for required evaluation but
its definition does not include a test expression to evaluate.
% DHCP6_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.
% DHCP6_CB_FETCH_UPDATES_RETRIES_EXHAUSTED maximum number of configuration fetch attempts: 10, has been exhausted without success
This error indicates that the server has made a number of unsuccessful
attempts to fetch configuration updates from a configuration backend.
The server will continue to operate but won't make any further attempts
to fetch configuration updates. The administrator must fix the configuration
in the database and reload (or restart) the server.
% DHCP6_COMMAND_RECEIVED received command %1, arguments: %2
A debug message listing the command (and possible arguments) received
from the Kea control system by the IPv6 DHCP server.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -53,6 +53,8 @@ using namespace std;
DATA_DIRECTORY "data-directory"
CONFIG_CONTROL "config-control"
CONFIG_DATABASES "config-databases"
CONFIG_FETCH_WAIT_TIME "config-fetch-wait-time"
INTERFACES_CONFIG "interfaces-config"
INTERFACES "interfaces"
RE_DETECT "re-detect"
@ -2219,6 +2221,7 @@ config_control_params: config_control_param
// This defines a list of allowed parameters for each subnet.
config_control_param: config_databases
| config_fetch_wait_time
;
config_databases: CONFIG_DATABASES {
@ -2231,6 +2234,11 @@ config_databases: CONFIG_DATABASES {
ctx.leave();
};
config_fetch_wait_time: CONFIG_FETCH_WAIT_TIME COLON INTEGER {
ElementPtr value(new IntElement($3, ctx.loc2pos(@3)));
ctx.stack_.back()->set("config-fetch-wait-time", value);
};
// --- logging entry -----------------------------------------
// This defines the top level "Logging" object. It parses

View File

@ -183,9 +183,8 @@ Dhcpv6Srv::Dhcpv6Srv(uint16_t server_port, uint16_t client_port)
: io_service_(new IOService()), server_port_(server_port),
client_port_(client_port), serverid_(), shutdown_(true),
alloc_engine_(), name_change_reqs_(),
network_state_(new NetworkState(NetworkState::DHCPv6))
{
network_state_(new NetworkState(NetworkState::DHCPv6)),
cb_control_(new CBControlDHCPv6()) {
LOG_DEBUG(dhcp6_logger, DBG_DHCP6_START, DHCP6_OPEN_SOCKET)
.arg(server_port);

View File

@ -1,4 +1,4 @@
// Copyright (C) 2011-2018 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2011-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
@ -18,6 +18,7 @@
#include <dhcp/pkt6.h>
#include <dhcpsrv/alloc_engine.h>
#include <dhcpsrv/callout_handle_store.h>
#include <dhcpsrv/cb_ctl_dhcp6.h>
#include <dhcpsrv/cfg_option.h>
#include <dhcpsrv/d2_client_mgr.h>
#include <dhcpsrv/network_state.h>
@ -88,6 +89,14 @@ public:
/// @brief Destructor. Used during DHCPv6 service shutdown.
virtual ~Dhcpv6Srv();
/// @brief Checks if the server is running in unit test mode.
///
/// @return true if the server is running in unit test mode,
/// false otherwise.
bool inTestMode() const {
return (server_port_ == 0);
}
/// @brief Returns pointer to the IO service used by the server.
asiolink::IOServicePtr& getIOService() {
return (io_service_);
@ -98,6 +107,15 @@ public:
return (network_state_);
}
/// @brief Returns an object which controls access to the configuration
/// backends.
///
/// @return Pointer to the instance of the object which controls
/// access to the configuration backends.
CBControlDHCPv6Ptr getCBControl() const {
return (cb_control_);
}
/// @brief returns Kea version on stdout and exit.
/// redeclaration/redefinition. @ref isc::process::Daemon::getVersion()
static std::string getVersion(bool extended);
@ -998,6 +1016,8 @@ protected:
/// disabled subnet/network scopes.
NetworkStatePtr network_state_;
/// @brief Controls access to the configuration backends.
CBControlDHCPv6Ptr cb_control_;
};
}; // namespace isc::dhcp

View File

@ -16,6 +16,7 @@
#include <dhcp6/dhcp6_log.h>
#include <dhcp6/dhcp6_srv.h>
#include <dhcp/iface_mgr.h>
#include <dhcpsrv/cb_ctl_dhcp4.h>
#include <dhcpsrv/cfg_option.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/db_type.h>
@ -419,6 +420,7 @@ configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set,
if (!check_only) {
TimerMgr::instance()->unregisterTimers();
server.discardPackets();
server.getCBControl()->reset();
}
// Revert any runtime option definitions configured so far and not committed.
@ -444,9 +446,10 @@ configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set,
// the parsers. It is declared outside the loop so in case of error, the
// name of the failing parser can be retrieved within the "catch" clause.
ConfigPair config_pair;
SrvConfigPtr srv_config;
try {
SrvConfigPtr srv_config = CfgMgr::instance().getStagingCfg();
// Get the staging configuration.
srv_config = CfgMgr::instance().getStagingCfg();
// Preserve all scalar global parameters
srv_config->extractConfiguredGlobals(config_set);
@ -739,6 +742,10 @@ configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set,
const HooksConfig& libraries =
CfgMgr::instance().getStagingCfg()->getHooksConfig();
libraries.loadLibraries();
// If there are config backends, fetch and merge into staging config
server.getCBControl()->databaseConfigFetch(srv_config,
CBControlDHCPv6::FetchMode::FETCH_ALL);
}
catch (const isc::Exception& ex) {
LOG_ERROR(dhcp6_logger, DHCP6_PARSER_COMMIT_FAIL).arg(ex.what());

View File

@ -1,9 +1,8 @@
// Generated 201903231444
// A Bison parser, made by GNU Bison 3.3.2.
// A Bison parser, made by GNU Bison 3.2.1.
// Locations for Bison parsers in C++
// Copyright (C) 2002-2015, 2018-2019 Free Software Foundation, Inc.
// Copyright (C) 2002-2015, 2018 Free Software Foundation, Inc.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by

View File

@ -1,5 +1,4 @@
// Generated 201903231444
// A Bison parser, made by GNU Bison 3.3.2.
// A Bison parser, made by GNU Bison 3.2.1.
// Starting with Bison 3.2, this file is useless: the structure it
// used to define is now defined in "location.hh".

View File

@ -1,5 +1,4 @@
// Generated 201903231444
// A Bison parser, made by GNU Bison 3.3.2.
// A Bison parser, made by GNU Bison 3.2.1.
// Starting with Bison 3.2, this file is useless: the structure it
// used to define is now defined with the parser itself.

View File

@ -0,0 +1,510 @@
// Copyright (C) 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
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <config.h>
#include <arpa/inet.h>
#include <gtest/gtest.h>
#include <database/backend_selector.h>
#include <dhcp/tests/iface_mgr_test_config.h>
#include <dhcp6/dhcp6_srv.h>
#include <dhcp6/ctrl_dhcp6_srv.h>
#include <dhcp6/json_config_parser.h>
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/cfg_subnets6.h>
#include <dhcpsrv/testutils/generic_backend_unittest.h>
#include <dhcpsrv/testutils/test_config_backend_dhcp6.h>
#include "dhcp6_test_utils.h"
#include "get_config_unittest.h"
#include <boost/foreach.hpp>
#include <boost/scoped_ptr.hpp>
#include <iostream>
#include <fstream>
#include <sstream>
#include <limits.h>
using namespace isc::asiolink;
using namespace isc::config;
using namespace isc::data;
using namespace isc::dhcp;
using namespace isc::dhcp::test;
using namespace isc::db;
using namespace std;
namespace {
/// @brief Test fixture for testing external configuration merging
class Dhcp6CBTest : public GenericBackendTest {
protected:
/// @brief Pre test set up
/// Called prior to each test. It creates two configuration backends
/// that differ by host name ("db1" and "db2"). It then registers
/// a backend factory that will return them rather than create
/// new instances. The backends need to pre-exist so they can be
/// populated prior to calling server configure. It uses
/// TestConfigBackend instances but with a type of "memfile" to pass
/// parsing. Doing it all here allows us to use ASSERTs if we feel like
/// it.
virtual void SetUp() {
DatabaseConnection::ParameterMap params;
params[std::string("type")] = std::string("memfile");
params[std::string("host")] = std::string("db1");
db1_.reset(new TestConfigBackendDHCPv6(params));
params[std::string("host")] = std::string("db2");
db2_.reset(new TestConfigBackendDHCPv6(params));
ConfigBackendDHCPv6Mgr::instance().registerBackendFactory("memfile",
[this](const DatabaseConnection::ParameterMap& params)
-> ConfigBackendDHCPv6Ptr {
auto host = params.find("host");
if (host != params.end()) {
if (host->second == "db1") {
return (db1_);
} else if (host->second == "db2") {
return (db2_);
}
}
// Apparently we're looking for one that does not prexist.
return (TestConfigBackendDHCPv6Ptr(new TestConfigBackendDHCPv6(params)));
});
}
/// @brief Clean up after each test
virtual void TearDown() {
// Unregister the factory to be tidy.
ConfigBackendDHCPv6Mgr::instance().unregisterBackendFactory("memfile");
}
public:
/// Constructor
Dhcp6CBTest()
: rcode_(-1), db1_selector("db1"), db2_selector("db1") {
// Open port 0 means to not do anything at all. We don't want to
// deal with sockets here, just check if configuration handling
// is sane.
srv_.reset(new ControlledDhcpv6Srv(0));
// Create fresh context.
resetConfiguration();
}
/// Destructor
virtual ~Dhcp6CBTest() {
resetConfiguration();
};
/// @brief Reset configuration singletons.
void resetConfiguration() {
CfgMgr::instance().clear();
ConfigBackendDHCPv6Mgr::destroy();
}
/// @brief Convenience method for running configuration
///
/// This method does not throw, but signals errors using gtest macros.
///
/// @param config text to be parsed as JSON
/// @param expected_code expected code (see cc/command_interpreter.h)
/// @param exp_error expected text error (check skipped if empty)
void configure(std::string config, int expected_code,
std::string exp_error = "") {
ConstElementPtr json;
try {
json = parseDHCP6(config, true);
} catch(const std::exception& ex) {
ADD_FAILURE() << "parseDHCP6 failed: " << ex.what();
}
ConstElementPtr status;
ASSERT_NO_THROW(status = configureDhcp6Server(*srv_, json));
ASSERT_TRUE(status);
int rcode;
ConstElementPtr comment = parseAnswer(rcode, status);
ASSERT_EQ(expected_code, rcode) << " comment: "
<< comment->stringValue();
string text;
ASSERT_NO_THROW(text = comment->stringValue());
if (expected_code != rcode) {
std::cout << "Reported status: " << text << std::endl;
}
if ((rcode != 0)) {
if (!exp_error.empty()) {
ASSERT_EQ(exp_error, text);
}
}
}
boost::scoped_ptr<Dhcpv6Srv> srv_; ///< DHCP6 server under test
int rcode_; ///< Return code from element parsing
ConstElementPtr comment_; ///< Reason for parse fail
BackendSelector db1_selector; ///< BackendSelector by host for first config backend
BackendSelector db2_selector; ///< BackendSelector by host for second config backend
TestConfigBackendDHCPv6Ptr db1_; ///< First configuration backend instance
TestConfigBackendDHCPv6Ptr db2_; ///< Second configuration backend instance
};
// This test verifies that externally configured globals are
// merged correctly into staging configuration.
TEST_F(Dhcp6CBTest, mergeGlobals) {
string base_config =
"{ \n"
" \"interfaces-config\": { \n"
" \"interfaces\": [\"*\" ] \n"
" }, \n"
" \"echo-client-id\": false, \n"
" \"decline-probation-period\": 7000, \n"
" \"valid-lifetime\": 1000, \n"
" \"rebind-timer\": 800, \n"
" \"server-hostname\": \"overwrite.me.com\", \n"
" \"config-control\": { \n"
" \"config-databases\": [ { \n"
" \"type\": \"memfile\", \n"
" \"host\": \"db1\" \n"
" },{ \n"
" \"type\": \"memfile\", \n"
" \"host\": \"db2\" \n"
" } \n"
" ] \n"
" } \n"
"} \n";
extractConfig(base_config);
// Make some globals:
StampedValuePtr server_hostname(new StampedValue("server-hostname", "isc.example.org"));
StampedValuePtr decline_period(new StampedValue("decline-probation-period", Element::create(86400)));
StampedValuePtr calc_tee_times(new StampedValue("calculate-tee-times", Element::create(bool(false))));
StampedValuePtr t2_percent(new StampedValue("t2-percent", Element::create(0.75)));
StampedValuePtr renew_timer(new StampedValue("renew-timer", Element::create(500)));
// Let's add all of the globals to the second backend. This will verify
// we find them there.
db2_->createUpdateGlobalParameter6(ServerSelector::ALL(), server_hostname);
db2_->createUpdateGlobalParameter6(ServerSelector::ALL(), decline_period);
db2_->createUpdateGlobalParameter6(ServerSelector::ALL(), calc_tee_times);
db2_->createUpdateGlobalParameter6(ServerSelector::ALL(), t2_percent);
db2_->createUpdateGlobalParameter6(ServerSelector::ALL(), renew_timer);
// Should parse and merge without error.
ASSERT_NO_FATAL_FAILURE(configure(base_config, CONTROL_RESULT_SUCCESS, ""));
// Verify the composite staging is correct. (Remember that
// CfgMgr::instance().commit() hasn't been called)
SrvConfigPtr staging_cfg = CfgMgr::instance().getStagingCfg();
// echo-client-id is set explicitly in the original config, meanwhile
// the backend config does not set it, so the explicit value wins.
EXPECT_FALSE(staging_cfg->getEchoClientId());
// decline-probation-period is an explicit member that should come
// from the backend.
EXPECT_EQ(86400, staging_cfg->getDeclinePeriod());
// Verify that the implicit globals from JSON are there.
ASSERT_NO_FATAL_FAILURE(checkConfiguredGlobal(staging_cfg, "valid-lifetime",
Element::create(1000)));
ASSERT_NO_FATAL_FAILURE(checkConfiguredGlobal(staging_cfg, "rebind-timer",
Element::create(800)));
// Verify that the implicit globals from the backend are there.
ASSERT_NO_FATAL_FAILURE(checkConfiguredGlobal(staging_cfg, server_hostname));
ASSERT_NO_FATAL_FAILURE(checkConfiguredGlobal(staging_cfg, calc_tee_times));
ASSERT_NO_FATAL_FAILURE(checkConfiguredGlobal(staging_cfg, t2_percent));
ASSERT_NO_FATAL_FAILURE(checkConfiguredGlobal(staging_cfg, renew_timer));
}
#if 0
// This test verifies that externally configured option definitions
// merged correctly into staging configuration.
TEST_F(Dhcp6CBTest, mergeOptionDefs) {
string base_config =
"{ \n"
" \"option-def\": [ { \n"
" \"name\": \"one\", \n"
" \"code\": 1, \n"
" \"type\": \"ipv6-address\", \n"
" \"space\": \"isc\" \n"
" }, \n"
" { \n"
" \"name\": \"two\", \n"
" \"code\": 2, \n"
" \"type\": \"string\", \n"
" \"space\": \"isc\" \n"
" } \n"
" ], \n"
" \"config-control\": { \n"
" \"config-databases\": [ { \n"
" \"type\": \"memfile\", \n"
" \"host\": \"db1\" \n"
" },{ \n"
" \"type\": \"memfile\", \n"
" \"host\": \"db2\" \n"
" } \n"
" ] \n"
" } \n"
"} \n";
extractConfig(base_config);
// Create option one replacement and add it to first backend.
OptionDefinitionPtr def;
def.reset(new OptionDefinition("one", 101, "uint16"));
def->setOptionSpaceName("isc");
db1_->createUpdateOptionDef6(ServerSelector::ALL(), def);
// Create option three and add it to first backend.
def.reset(new OptionDefinition("three", 3, "string"));
def->setOptionSpaceName("isc");
db1_->createUpdateOptionDef6(ServerSelector::ALL(), def);
// Create option four and add it to second backend.
def.reset(new OptionDefinition("four", 4, "string"));
def->setOptionSpaceName("isc");
db2_->createUpdateOptionDef6(ServerSelector::ALL(), def);
// Should parse and merge without error.
ASSERT_NO_FATAL_FAILURE(configure(base_config, CONTROL_RESULT_SUCCESS, ""));
// Verify the composite staging is correct.
SrvConfigPtr staging_cfg = CfgMgr::instance().getStagingCfg();
ConstCfgOptionDefPtr option_defs = staging_cfg->getCfgOptionDef();
// Definition "one" from first backend should be there.
OptionDefinitionPtr found_def = option_defs->get("isc", "one");
ASSERT_TRUE(found_def);
EXPECT_EQ(101, found_def->getCode());
EXPECT_EQ(OptionDataType::OPT_UINT16_TYPE, found_def->getType());
// Definition "two" from JSON config should be there.
found_def = option_defs->get("isc", "two");
ASSERT_TRUE(found_def);
EXPECT_EQ(2, found_def->getCode());
// Definition "three" from first backend should be there.
found_def = option_defs->get("isc", "three");
ASSERT_TRUE(found_def);
EXPECT_EQ(3, found_def->getCode());
// Definition "four" from first backend should not be there.
found_def = option_defs->get("isc", "four");
ASSERT_FALSE(found_def);
}
// This test verifies that externally configured options
// merged correctly into staging configuration.
TEST_F(Dhcp6CBTest, mergeOptions) {
string base_config =
"{ \n"
" \"option-data\": [ { \n"
" \"name\": \"dhcp-message\", \n"
" \"data\": \"0A0B0C0D\", \n"
" \"csv-format\": false \n"
" },{ \n"
" \"name\": \"host-name\", \n"
" \"data\": \"old.example.com\", \n"
" \"csv-format\": true \n"
" } \n"
" ], \n"
" \"config-control\": { \n"
" \"config-databases\": [ { \n"
" \"type\": \"memfile\", \n"
" \"host\": \"db1\" \n"
" },{ \n"
" \"type\": \"memfile\", \n"
" \"host\": \"db2\" \n"
" } \n"
" ] \n"
" } \n"
"} \n";
extractConfig(base_config);
OptionDescriptorPtr opt;
// Add host-name to the first backend.
opt.reset(new OptionDescriptor(
createOption<OptionString>(Option::V6, DHO_HOST_NAME,
true, false, "new.example.com")));
opt->space_name_ = DHCP6_OPTION_SPACE;
db1_->createUpdateOption6(ServerSelector::ALL(), opt);
// Add boot-file-name to the first backend.
opt.reset(new OptionDescriptor(
createOption<OptionString>(Option::V6, DHO_BOOT_FILE_NAME,
true, false, "my-boot-file")));
opt->space_name_ = DHCP6_OPTION_SPACE;
db1_->createUpdateOption6(ServerSelector::ALL(), opt);
// Add boot-file-name to the second backend.
opt.reset(new OptionDescriptor(
createOption<OptionString>(Option::V6, DHO_BOOT_FILE_NAME,
true, false, "your-boot-file")));
opt->space_name_ = DHCP6_OPTION_SPACE;
db2_->createUpdateOption6(ServerSelector::ALL(), opt);
// Should parse and merge without error.
ASSERT_NO_FATAL_FAILURE(configure(base_config, CONTROL_RESULT_SUCCESS, ""));
// Verify the composite staging is correct.
SrvConfigPtr staging_cfg = CfgMgr::instance().getStagingCfg();
// Option definition from JSON should be there.
CfgOptionPtr options = staging_cfg->getCfgOption();
// dhcp-message should come from the original config.
OptionDescriptor found_opt = options->get("dhcp6", DHO_DHCP_MESSAGE);
ASSERT_TRUE(found_opt.option_);
EXPECT_EQ("0x0A0B0C0D", found_opt.option_->toHexString());
// host-name should come from the first back end,
// (overwriting the original).
found_opt = options->get("dhcp6", DHO_HOST_NAME);
ASSERT_TRUE(found_opt.option_);
EXPECT_EQ("new.example.com", found_opt.option_->toString());
// booth-file-name should come from the first back end.
found_opt = options->get("dhcp6", DHO_BOOT_FILE_NAME);
ASSERT_TRUE(found_opt.option_);
EXPECT_EQ("my-boot-file", found_opt.option_->toString());
}
// This test verifies that externally configured shared-networks are
// merged correctly into staging configuration.
TEST_F(Dhcp6CBTest, mergeSharedNetworks) {
string base_config =
"{ \n"
" \"interfaces-config\": { \n"
" \"interfaces\": [\"*\" ] \n"
" }, \n"
" \"valid-lifetime\": 4000, \n"
" \"config-control\": { \n"
" \"config-databases\": [ { \n"
" \"type\": \"memfile\", \n"
" \"host\": \"db1\" \n"
" },{ \n"
" \"type\": \"memfile\", \n"
" \"host\": \"db2\" \n"
" } \n"
" ] \n"
" }, \n"
" \"shared-networks\": [ { \n"
" \"name\": \"two\" \n"
" }] \n"
"} \n";
extractConfig(base_config);
// Make a few networks
SharedNetwork6Ptr network1(new SharedNetwork6("one"));
SharedNetwork6Ptr network3(new SharedNetwork6("three"));
// Add network1 to db1 and network3 to db2
db1_->createUpdateSharedNetwork6(ServerSelector::ALL(), network1);
db2_->createUpdateSharedNetwork6(ServerSelector::ALL(), network3);
// Should parse and merge without error.
ASSERT_NO_FATAL_FAILURE(configure(base_config, CONTROL_RESULT_SUCCESS, ""));
// Verify the composite staging is correct. (Remember that
// CfgMgr::instance().commit() hasn't been called)
SrvConfigPtr staging_cfg = CfgMgr::instance().getStagingCfg();
CfgSharedNetworks4Ptr networks = staging_cfg->getCfgSharedNetworks4();
SharedNetwork6Ptr staged_network;
// SharedNetwork One should have been added from db1 config
staged_network = networks->getByName("one");
ASSERT_TRUE(staged_network);
// Subnet2 should have come from the json config
staged_network = networks->getByName("two");
ASSERT_TRUE(staged_network);
// Subnet3, which is in db2 should not have been merged.
// We queried db1 first and the query returned data. In
// other words, we iterate over the backends, asking for
// data. We use the first data, we find.
staged_network = networks->getByName("three");
ASSERT_FALSE(staged_network);
}
// This test verifies that externally configured subnets are
// merged correctly into staging configuration.
TEST_F(Dhcp6CBTest, mergeSubnets) {
string base_config =
"{ \n"
" \"interfaces-config\": { \n"
" \"interfaces\": [\"*\" ] \n"
" }, \n"
" \"valid-lifetime\": 4000, \n"
" \"config-control\": { \n"
" \"config-databases\": [ { \n"
" \"type\": \"memfile\", \n"
" \"host\": \"db1\" \n"
" },{ \n"
" \"type\": \"memfile\", \n"
" \"host\": \"db2\" \n"
" } \n"
" ] \n"
" }, \n"
" \"subnet6\": [ \n"
" { \n"
" \"id\": 2,\n"
" \"subnet\": \"192.0.3.0/24\" \n"
" } ]\n"
"} \n";
extractConfig(base_config);
// Make a few subnets
Subnet6Ptr subnet1(new Subnet6(IOAddress("192.0.2.0"), 26, 1, 2, 3, SubnetID(1)));
Subnet6Ptr subnet3(new Subnet6(IOAddress("192.0.4.0"), 26, 1, 2, 3, SubnetID(3)));
// Add subnet1 to db1 and subnet3 to db2
db1_->createUpdateSubnet6(ServerSelector::ALL(), subnet1);
db2_->createUpdateSubnet6(ServerSelector::ALL(), subnet3);
// Should parse and merge without error.
configure(base_config, CONTROL_RESULT_SUCCESS, "");
// Verify the composite staging is correct. (Remember that
// CfgMgr::instance().commit() hasn't been called)
SrvConfigPtr staging_cfg = CfgMgr::instance().getStagingCfg();
CfgSubnets6Ptr subnets = staging_cfg->getCfgSubnets6();
Subnet6Ptr staged_subnet;
// Subnet1 should have been added from db1 config
staged_subnet = subnets->getSubnet(1);
ASSERT_TRUE(staged_subnet);
// Subnet2 should have come from the json config
staged_subnet = subnets->getSubnet(2);
ASSERT_TRUE(staged_subnet);
// Subnet3, which is in db2 should not have been merged, since it is
// first found, first used?
staged_subnet = subnets->getSubnet(3);
ASSERT_FALSE(staged_subnet);
}
#endif
}

View File

@ -25,6 +25,7 @@
#include <dhcpsrv/subnet.h>
#include <dhcpsrv/subnet_selector.h>
#include <dhcpsrv/testutils/config_result_check.h>
#include <dhcpsrv/testutils/test_config_backend_dhcp6.h>
#include <hooks/hooks_manager.h>
#include <process/config_ctl_info.h>
@ -242,6 +243,7 @@ const char* PARSER_CONFIGS[] = {
" \"rebind-timer\": 2000, \n"
" \"renew-timer\": 1000, \n"
" \"config-control\": { \n"
" \"config-fetch-wait-time\": 10, \n"
" \"config-databases\": [ { \n"
" \"type\": \"mysql\", \n"
" \"name\": \"keatest1\", \n"
@ -6990,7 +6992,12 @@ TEST_F(Dhcp6ParserTest, globalReservations) {
// This test verifies that configuration control info gets populated.
TEST_F(Dhcp6ParserTest, configControlInfo) {
string config = PARSER_CONFIGS[8];
extractConfig(config);
// Should be able to register a backend factory for "mysql".
ASSERT_TRUE(TestConfigBackendDHCPv6::
registerBackendType(ConfigBackendDHCPv6Mgr::instance(),
"mysql"));
configure(config, CONTROL_RESULT_SUCCESS, "");
// Make sure the config control info is there.
@ -7009,6 +7016,10 @@ TEST_F(Dhcp6ParserTest, configControlInfo) {
dblist.front().getAccessString());
EXPECT_EQ("name=keatest2 password=keatest type=mysql user=keatest",
dblist.back().getAccessString());
// Verify that the config-fetch-wait-time is correct.
EXPECT_FALSE(info->getConfigFetchWaitTime().unspecified());
EXPECT_EQ(10, info->getConfigFetchWaitTime().get());
}
// Check whether it is possible to configure server-tag

View File

@ -1829,32 +1829,6 @@ const char* EXTRACTED_CONFIGS[] = {
" }\n"
" ],\n"
" \"valid-lifetime\": 4000\n"
" }\n",
// CONFIGURATION 58
"{\n"
" \"config-control\": {\n"
" \"config-databases\": [\n"
" {\n"
" \"name\": \"keatest1\",\n"
" \"password\": \"keatest\",\n"
" \"type\": \"mysql\",\n"
" \"user\": \"keatest\"\n"
" },\n"
" {\n"
" \"name\": \"keatest2\",\n"
" \"password\": \"keatest\",\n"
" \"type\": \"mysql\",\n"
" \"user\": \"keatest\"\n"
" }\n"
" ]\n"
" },\n"
" \"interfaces-config\": {\n"
" \"interfaces\": [ \"*\" ],\n"
" \"re-detect\": false\n"
" },\n"
" \"rebind-timer\": 2000,\n"
" \"renew-timer\": 1000,\n"
" \"valid-lifetime\": 4000\n"
" }\n"
};
@ -7717,86 +7691,6 @@ const char* UNPARSED_CONFIGS[] = {
" }\n"
" ],\n"
" \"valid-lifetime\": 4000\n"
" }\n",
// CONFIGURATION 58
"{\n"
" \"config-control\": {\n"
" \"config-databases\": [\n"
" {\n"
" \"name\": \"keatest1\",\n"
" \"password\": \"keatest\",\n"
" \"type\": \"mysql\",\n"
" \"user\": \"keatest\"\n"
" },\n"
" {\n"
" \"name\": \"keatest2\",\n"
" \"password\": \"keatest\",\n"
" \"type\": \"mysql\",\n"
" \"user\": \"keatest\"\n"
" }\n"
" ]\n"
" },\n"
" \"decline-probation-period\": 86400,\n"
" \"dhcp-ddns\": {\n"
" \"enable-updates\": false,\n"
" \"generated-prefix\": \"myhost\",\n"
" \"hostname-char-replacement\": \"\",\n"
" \"hostname-char-set\": \"\",\n"
" \"max-queue-size\": 1024,\n"
" \"ncr-format\": \"JSON\",\n"
" \"ncr-protocol\": \"UDP\",\n"
" \"override-client-update\": false,\n"
" \"override-no-update\": false,\n"
" \"qualifying-suffix\": \"\",\n"
" \"replace-client-name\": \"never\",\n"
" \"sender-ip\": \"0.0.0.0\",\n"
" \"sender-port\": 0,\n"
" \"server-ip\": \"127.0.0.1\",\n"
" \"server-port\": 53001\n"
" },\n"
" \"dhcp-queue-control\": {\n"
" \"capacity\": 500,\n"
" \"enable-queue\": false,\n"
" \"queue-type\": \"kea-ring6\"\n"
" },\n"
" \"dhcp4o6-port\": 0,\n"
" \"expired-leases-processing\": {\n"
" \"flush-reclaimed-timer-wait-time\": 25,\n"
" \"hold-reclaimed-time\": 3600,\n"
" \"max-reclaim-leases\": 100,\n"
" \"max-reclaim-time\": 250,\n"
" \"reclaim-timer-wait-time\": 10,\n"
" \"unwarned-reclaim-cycles\": 5\n"
" },\n"
" \"hooks-libraries\": [ ],\n"
" \"host-reservation-identifiers\": [ \"hw-address\", \"duid\" ],\n"
" \"interfaces-config\": {\n"
" \"interfaces\": [ \"*\" ],\n"
" \"re-detect\": false\n"
" },\n"
" \"lease-database\": {\n"
" \"type\": \"memfile\"\n"
" },\n"
" \"mac-sources\": [ \"any\" ],\n"
" \"option-data\": [ ],\n"
" \"option-def\": [ ],\n"
" \"rebind-timer\": 2000,\n"
" \"relay-supplied-options\": [ \"65\" ],\n"
" \"renew-timer\": 1000,\n"
" \"sanity-checks\": {\n"
" \"lease-checks\": \"warn\"\n"
" },\n"
" \"server-id\": {\n"
" \"enterprise-id\": 0,\n"
" \"htype\": 0,\n"
" \"identifier\": \"\",\n"
" \"persist\": true,\n"
" \"time\": 0,\n"
" \"type\": \"LLT\"\n"
" },\n"
" \"shared-networks\": [ ],\n"
" \"subnet6\": [ ],\n"
" \"valid-lifetime\": 4000\n"
" }\n"
};

View File

@ -14,9 +14,11 @@
#include <dhcp6/ctrl_dhcp6_srv.h>
#include <dhcp6/parser_context.h>
#include <dhcp6/tests/dhcp6_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>
#ifdef HAVE_MYSQL
#include <mysql/testutils/mysql_schema.h>
@ -25,9 +27,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 <sstream>
@ -51,10 +55,100 @@ using namespace isc::hooks;
namespace {
class NakedControlledDhcpv6Srv: public ControlledDhcpv6Srv {
// "Naked" DHCPv6 server, exposes internal fields
/// @brief Test implementation of the @c CBControlDHCPv6.
///
/// 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 TestCBControlDHCPv6 : public CBControlDHCPv6 {
public:
NakedControlledDhcpv6Srv():ControlledDhcpv6Srv(0) { }
/// @brief Constructor.
TestCBControlDHCPv6()
: CBControlDHCPv6(), db_config_fetch_calls_(0),
enable_check_fetch_mode_(false), 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_mode value indicating if the method is called upon the
/// server start up or it is called to fetch configuration updates.
///
/// @throw Unexpected when configured to do so.
virtual void databaseConfigFetch(const process::ConfigPtr&,
const FetchMode& fetch_mode) {
++db_config_fetch_calls_;
if (enable_check_fetch_mode_) {
if ((db_config_fetch_calls_ <= 1) && (fetch_mode == FetchMode::FETCH_UPDATE)) {
ADD_FAILURE() << "databaseConfigFetch was called with the value "
"of fetch_mode=FetchMode::FETCH_UPDATE upon the server configuration";
} else if ((db_config_fetch_calls_ > 1) && (fetch_mode == FetchMode::FETCH_ALL)) {
ADD_FAILURE() << "databaseConfigFetch was called with the value "
"of fetch_mode=FetchMode::FETCH_ALL during fetching the updates";
}
}
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 checking of the @c fetch_mode value.
void enableCheckFetchMode() {
enable_check_fetch_mode_ = true;
}
/// @brief Enables the object to throw from @c databaseConfigFetch.
void enableThrow() {
enable_throw_ = true;
}
private:
/// @brief Counter holding number of invocations of the @c databaseConfigFetch.
size_t db_config_fetch_calls_;
/// @brief Boolean flag indicated if the value of the @c fetch_mode
/// should be verified.
bool enable_check_fetch_mode_;
/// @brief Boolean flag indicating if the @c databaseConfigFetch should
/// throw.
bool enable_throw_;
};
/// @brief Shared pointer to the @c TestCBControlDHCPv6.
typedef boost::shared_ptr<TestCBControlDHCPv6> TestCBControlDHCPv6Ptr;
/// @brief "Naked" DHCPv6 server.
///
/// Exposes internal fields and installs stub implementation of the
/// @c CBControlDHCPv6 object.
class NakedControlledDhcpv6Srv: public ControlledDhcpv6Srv {
public:
/// @brief Constructor.
NakedControlledDhcpv6Srv()
: ControlledDhcpv6Srv(0) {
// We're replacing the @c CBControlDHCPv6 instance with our
// stub implementation used in tests.
cb_control_.reset(new TestCBControlDHCPv6());
}
using ControlledDhcpv6Srv::signal_handler_;
};
@ -85,15 +179,105 @@ 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]() {
io_service->stop();
stopped = true;
}, timeout_ms, IntervalTimer::ONE_SHOT);
io_service->run();
// Run as long as the timeout hasn't occurred and the interrupting
// condition is not specified or not met.
while (!stopped && (!cond || !cond())) {
io_service->run_one();
}
io_service->get_io_service().reset();
}
/// @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 <<
"{ \"Dhcp6\": {"
"\"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"
"\"subnet6\": [ ],"
"\"valid-lifetime\": 4000 }"
"}";
writeFile(TEST_FILE, config.str());
// Create an instance of the server and initialize it.
boost::scoped_ptr<NakedControlledDhcpv6Srv> srv;
ASSERT_NO_THROW(srv.reset(new NakedControlledDhcpv6Srv()));
ASSERT_NO_THROW(srv->init(TEST_FILE));
// Get the CBControlDHCPv6 object belonging to this server.
auto cb_control = boost::dynamic_pointer_cast<TestCBControlDHCPv6>(srv->getCBControl());
// Verify that the parameter passed to the databaseConfigFetch has an
// expected value.
cb_control->enableCheckFetchMode();
// Instruct our stub implementation of the CBControlDHCPv6 to throw as a
// result of fetch if desired.
if (throw_during_fetch) {
cb_control->enableThrow();
}
// 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) && (!throw_during_fetch)) {
// 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 {
ASSERT_NO_THROW(runTimersWithTimeout(srv->getIOService(), 500));
if (throw_during_fetch) {
// If we're simulating the failure condition the number
// of consecutive failures should not exceed 10. Therefore
// the number of recorded fetches should be 12. One at
// startup, 10 failures and one that causes the timer
// to stop.
EXPECT_EQ(12, cb_control->getDatabaseConfigFetchCalls());
} else {
// If the server is not configured to schedule the timer,
// we should still have one fetch attempt recorded.
EXPECT_EQ(1, cb_control->getDatabaseConfigFetchCalls());
}
}
}
static const char* TEST_FILE;
static const char* TEST_INCLUDE;
};
@ -635,6 +819,29 @@ TEST_F(JSONFileBackendTest, defaultLeaseDbBackend) {
EXPECT_NO_THROW(static_cast<void>(LeaseMgrFactory::instance()));
}
// 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) {
testConfigBackendTimer(1);
}
// 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) {
testConfigBackendTimer(0);
}
// This test verifies that the server will gracefully handle exceptions
// thrown from the CBControlDHCPv6::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.

View File

@ -68,6 +68,7 @@ libkea_dhcpsrv_la_SOURCES += cache_host_data_source.h
libkea_dhcpsrv_la_SOURCES += callout_handle_store.h
libkea_dhcpsrv_la_SOURCES += cb_ctl_dhcp.h
libkea_dhcpsrv_la_SOURCES += cb_ctl_dhcp4.cc cb_ctl_dhcp4.h
libkea_dhcpsrv_la_SOURCES += cb_ctl_dhcp6.cc cb_ctl_dhcp6.h
libkea_dhcpsrv_la_SOURCES += cfg_4o6.cc cfg_4o6.h
libkea_dhcpsrv_la_SOURCES += cfg_consistency.cc cfg_consistency.h
libkea_dhcpsrv_la_SOURCES += cfg_db_access.cc cfg_db_access.h

View File

@ -0,0 +1,85 @@
// Copyright (C) 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
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <config.h>
#include <dhcpsrv/cb_ctl_dhcp6.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/dhcpsrv_log.h>
using namespace isc::data;
using namespace isc::process;
namespace isc {
namespace dhcp {
void
CBControlDHCPv6::databaseConfigApply(const db::BackendSelector& backend_selector,
const db::ServerSelector& server_selector,
const boost::posix_time::ptime& lb_modification_time,
const db::AuditEntryCollection& audit_entries) {
// Create the external config into which we'll fetch backend config data.
SrvConfigPtr external_cfg = CfgMgr::instance().createExternalCfg();
// First let's fetch the globals and add them to external config.
if (fetchConfigElement(audit_entries, "dhcp6_global_parameter")) {
data::StampedValueCollection globals;
globals = getMgr().getPool()->getModifiedGlobalParameters6(backend_selector, server_selector,
lb_modification_time);
addGlobalsToConfig(external_cfg, globals);
}
// Now we fetch the option definitions and add them.
if (fetchConfigElement(audit_entries, "dhcp6_option_def")) {
OptionDefContainer option_defs =
getMgr().getPool()->getModifiedOptionDefs6(backend_selector, server_selector,
lb_modification_time);
for (auto option_def = option_defs.begin(); option_def != option_defs.end(); ++option_def) {
external_cfg->getCfgOptionDef()->add((*option_def), (*option_def)->getOptionSpaceName());
}
}
// Next fetch the options. They are returned as a container of OptionDescriptors.
if (fetchConfigElement(audit_entries, "dhcp6_options")) {
OptionContainer options = getMgr().getPool()->getModifiedOptions6(backend_selector,
server_selector,
lb_modification_time);
for (auto option = options.begin(); option != options.end(); ++option) {
external_cfg->getCfgOption()->add((*option), (*option).space_name_);
}
}
// Now fetch the shared networks.
if (fetchConfigElement(audit_entries, "dhcp6_shared_network")) {
SharedNetwork6Collection networks =
getMgr().getPool()->getModifiedSharedNetworks6(backend_selector, server_selector,
lb_modification_time);
for (auto network = networks.begin(); network != networks.end(); ++network) {
external_cfg->getCfgSharedNetworks6()->add((*network));
}
}
// Next we fetch subnets.
if (fetchConfigElement(audit_entries, "dhcp6_subnet")) {
Subnet6Collection subnets = getMgr().getPool()->getModifiedSubnets6(backend_selector,
server_selector,
lb_modification_time);
for (auto subnet = subnets.begin(); subnet != subnets.end(); ++subnet) {
external_cfg->getCfgSubnets6()->add((*subnet));
}
}
if (audit_entries.empty()) {
CfgMgr::instance().mergeIntoStagingCfg(external_cfg->getSequence());
} else {
CfgMgr::instance().mergeIntoCurrentCfg(external_cfg->getSequence());
}
LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_CONFIG6_MERGED);
}
} // end of namespace isc::dhcp
} // end of namespace isc

View File

@ -0,0 +1,50 @@
// Copyright (C) 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
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#ifndef CB_CTL_DHCP6_H
#define CB_CTL_DHCP6_H
#include <dhcpsrv/cb_ctl_dhcp.h>
#include <dhcpsrv/config_backend_dhcp6_mgr.h>
#include <dhcpsrv/srv_config.h>
namespace isc {
namespace dhcp {
/// @brief Implementation of the mechanisms to control the use of
/// the Configuration Backends by the DHCPv6 server.
///
/// It implements fetching and merging DHCPv6 server configuration from
/// the database into the staging or current configuration.
///
/// @tparam ConfigBackendMgrType Type of the Config Backend Manager used
/// by the server implementing this class. For example, for the DHCPv6
/// server it will be @c ConfigBackendDHCPv6Mgr.
class CBControlDHCPv6 : public CBControlDHCP<ConfigBackendDHCPv6Mgr> {
protected:
/// @brief DHCPv6 server specific method to fetch and apply back end
/// configuration into the local configuration.
///
/// @param backend_selector Backend selector.
/// @param server_selector Server selector.
/// @param lb_modification_time Lower bound modification time for the
/// configuration elements to be fetched.
/// @param audit_entries Audit entries fetched from the database since
/// the last configuration update. This collection is empty if there
/// were no updates.
virtual void databaseConfigApply(const db::BackendSelector& backend_selector,
const db::ServerSelector& server_selector,
const boost::posix_time::ptime& lb_modification_time,
const db::AuditEntryCollection& audit_entries);
};
typedef boost::shared_ptr<CBControlDHCPv6> CBControlDHCPv6Ptr;
} // end of namespace isc::dhcp
} // end of namespace isc
#endif // CB_CTL_DHCP6_H

View File

@ -1,4 +1,4 @@
// File created from ../../../src/lib/dhcpsrv/dhcpsrv_messages.mes on Tue Mar 19 2019 10:19
// File created from ../../../src/lib/dhcpsrv/dhcpsrv_messages.mes on Tue Mar 26 2019 13:08
#include <cstddef>
#include <log/message_types.h>
@ -14,6 +14,7 @@ extern const isc::log::MessageID DHCPSRV_CFGMGR_ALL_IFACES_ACTIVE = "DHCPSRV_CFG
extern const isc::log::MessageID DHCPSRV_CFGMGR_CFG_DHCP_DDNS = "DHCPSRV_CFGMGR_CFG_DHCP_DDNS";
extern const isc::log::MessageID DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES = "DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES";
extern const isc::log::MessageID DHCPSRV_CFGMGR_CONFIG4_MERGED = "DHCPSRV_CFGMGR_CONFIG4_MERGED";
extern const isc::log::MessageID DHCPSRV_CFGMGR_CONFIG6_MERGED = "DHCPSRV_CFGMGR_CONFIG6_MERGED";
extern const isc::log::MessageID DHCPSRV_CFGMGR_CONFIGURE_SERVERID = "DHCPSRV_CFGMGR_CONFIGURE_SERVERID";
extern const isc::log::MessageID DHCPSRV_CFGMGR_DEL_SUBNET4 = "DHCPSRV_CFGMGR_DEL_SUBNET4";
extern const isc::log::MessageID DHCPSRV_CFGMGR_DEL_SUBNET6 = "DHCPSRV_CFGMGR_DEL_SUBNET6";
@ -245,6 +246,7 @@ const char* values[] = {
"DHCPSRV_CFGMGR_CFG_DHCP_DDNS", "Setting DHCP-DDNS configuration to: %1",
"DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES", "stop listening on all interfaces",
"DHCPSRV_CFGMGR_CONFIG4_MERGED", "Configuration backend data has been merged.",
"DHCPSRV_CFGMGR_CONFIG6_MERGED", "Configuration backend data has been merged.",
"DHCPSRV_CFGMGR_CONFIGURE_SERVERID", "server configuration includes specification of a server identifier",
"DHCPSRV_CFGMGR_DEL_SUBNET4", "IPv4 subnet %1 removed",
"DHCPSRV_CFGMGR_DEL_SUBNET6", "IPv6 subnet %1 removed",

View File

@ -1,4 +1,4 @@
// File created from ../../../src/lib/dhcpsrv/dhcpsrv_messages.mes on Tue Mar 19 2019 10:19
// File created from ../../../src/lib/dhcpsrv/dhcpsrv_messages.mes on Tue Mar 26 2019 13:08
#ifndef DHCPSRV_MESSAGES_H
#define DHCPSRV_MESSAGES_H
@ -15,6 +15,7 @@ extern const isc::log::MessageID DHCPSRV_CFGMGR_ALL_IFACES_ACTIVE;
extern const isc::log::MessageID DHCPSRV_CFGMGR_CFG_DHCP_DDNS;
extern const isc::log::MessageID DHCPSRV_CFGMGR_CLEAR_ACTIVE_IFACES;
extern const isc::log::MessageID DHCPSRV_CFGMGR_CONFIG4_MERGED;
extern const isc::log::MessageID DHCPSRV_CFGMGR_CONFIG6_MERGED;
extern const isc::log::MessageID DHCPSRV_CFGMGR_CONFIGURE_SERVERID;
extern const isc::log::MessageID DHCPSRV_CFGMGR_DEL_SUBNET4;
extern const isc::log::MessageID DHCPSRV_CFGMGR_DEL_SUBNET6;

View File

@ -36,6 +36,11 @@ This is an informational message emitted when the DHCPv4 server has
successfully merged configuration data retrieved from its configuration
backends into the current configuration.
% DHCPSRV_CFGMGR_CONFIG6_MERGED Configuration backend data has been merged.
This is an informational message emitted when the DHCPv6 server has
successfully merged configuration data retrieved from its configuration
backends into the current configuration.
% DHCPSRV_CFGMGR_CONFIGURE_SERVERID server configuration includes specification of a server identifier
This warning message is issued when the server specified configuration of
a server identifier. If this new configuration overrides an existing