Commit c572f8ae authored by Thomas Markwalder's avatar Thomas Markwalder

[master] kea-dhcp4 now fetches data from config backends

    Merge branch '101-cb-add-two-step-configuration-to-the-dhcpv4-server-4'
parents 228eb27c a2c1d728
......@@ -120,12 +120,21 @@ new configuration. It is output during server startup, and when an updated
configuration is committed by the administrator. Additional information
may be provided.
% DHCP4_CONFIG_FETCH Fetching configuration data from config backends.
This is an informational message emitted when the DHCPv4 server about to begin
retrieving configuration data from one or more configuration backends.
% DHCP4_CONFIG_LOAD_FAIL configuration error using file: %1, reason: %2
This error message indicates that the DHCPv4 configuration has failed.
If this is an initial configuration (during server's startup) the server
will fail to start. If this is a dynamic reconfiguration attempt the
server will continue to use an old configuration.
% DHCP4_CONFIG_MERGED Configuration backend data has been merged.
This is an informational message emitted when the DHCPv4 server has
successfully merged configuration data retrieved from its configuration
backends into the current configuration.
% DHCP4_CONFIG_NEW_SUBNET a new subnet has been added to configuration: %1
This is an informational message reporting that the configuration has
been extended to include the specified IPv4 subnet.
......
......@@ -8,6 +8,8 @@
#include <cc/command_interpreter.h>
#include <database/dbaccess_parser.h>
#include <database/backend_selector.h>
#include <database/server_selector.h>
#include <dhcp4/dhcp4_log.h>
#include <dhcp4/dhcp4_srv.h>
#include <dhcp4/json_config_parser.h>
......@@ -54,6 +56,7 @@ using namespace isc::asiolink;
using namespace isc::hooks;
using namespace isc::process;
using namespace isc::config;
using namespace isc::db;
namespace {
......@@ -291,6 +294,7 @@ void configureCommandChannel() {
}
}
isc::data::ConstElementPtr
configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set,
bool check_only) {
......@@ -618,10 +622,8 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set,
CfgMgr::instance().getStagingCfg()->getHooksConfig();
libraries.loadLibraries();
#ifdef CONFIG_BACKEND // Disabled until we restart CB work
// If there are config backends, fetch and merge into staging config
databaseConfigFetch(srv_cfg, mutable_cfg);
#endif
databaseConfigFetch(srv_cfg);
}
catch (const isc::Exception& ex) {
LOG_ERROR(dhcp4_logger, DHCP4_PARSER_COMMIT_FAIL).arg(ex.what());
......@@ -654,6 +656,64 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set,
return (answer);
}
void databaseConfigFetch(const SrvConfigPtr& srv_cfg) {
ConfigBackendDHCPv4Mgr& mgr = ConfigBackendDHCPv4Mgr::instance();
// Close any existing CB databasess, then open all in srv_cfg (if any)
if (!databaseConfigConnect(srv_cfg)) {
// There are no CB databases so we're done
return;
}
LOG_INFO(dhcp4_logger, DHCP4_CONFIG_FETCH);
// For now we find data based on first backend that has it.
BackendSelector backend_selector(BackendSelector::Type::UNSPEC);
// Use the server_tag if set, otherwise use ALL.
std::string server_tag = srv_cfg->getServerTag();
ServerSelector& server_selector = (server_tag.empty()? ServerSelector::ALL()
: ServerSelector::ONE(server_tag));
// 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.
data::StampedValueCollection globals;
globals = mgr.getPool()->getAllGlobalParameters4(backend_selector, server_selector);
addGlobalsToConfig(external_cfg, globals);
// Now we fetch the option definitions and add them.
OptionDefContainer option_defs = mgr.getPool()->getAllOptionDefs4(backend_selector,
server_selector);
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.
OptionContainer options = mgr.getPool()->getAllOptions4(backend_selector, server_selector);
for (auto option = options.begin(); option != options.end(); ++option) {
external_cfg->getCfgOption()->add((*option), (*option).space_name_);
}
// Now fetch the shared networks.
SharedNetwork4Collection networks = mgr.getPool()->getAllSharedNetworks4(backend_selector,
server_selector);
for (auto network = networks.begin(); network != networks.end(); ++network) {
external_cfg->getCfgSharedNetworks4()->add((*network));
}
// Next we fetch subnets.
Subnet4Collection subnets = mgr.getPool()->getAllSubnets4(backend_selector, server_selector);
for (auto subnet = subnets.begin(); subnet != subnets.end(); ++subnet) {
external_cfg->getCfgSubnets4()->add((*subnet));
}
// Now we merge the fecthed configuration into the staging configuration.
CfgMgr::instance().mergeIntoStagingCfg(external_cfg->getSequence());
LOG_INFO(dhcp4_logger, DHCP4_CONFIG_MERGED);
}
bool databaseConfigConnect(const SrvConfigPtr& srv_cfg) {
// We need to get rid of any existing backends. These would be any
// opened by previous configuration cycle.
......@@ -678,25 +738,72 @@ bool databaseConfigConnect(const SrvConfigPtr& srv_cfg) {
return (true);
}
void databaseConfigFetch(const SrvConfigPtr& srv_cfg, ElementPtr /* mutable_cfg */) {
// Close any existing CB databasess, then open all in srv_cfg (if any)
if (!databaseConfigConnect(srv_cfg)) {
// There are no CB databases so we're done
return;
void addGlobalsToConfig(SrvConfigPtr external_cfg, data::StampedValueCollection& cb_globals) {
const auto& index = cb_globals.get<StampedValueNameIndexTag>();
for (auto cb_global = index.begin(); cb_global != index.end(); ++cb_global) {
// If the global is an explicit member of SrvConfig handle it that way.
if (handleExplicitGlobal(external_cfg, (*cb_global))) {
continue;
}
// Otherwise it must be added to the implicitly configured globals
if (handleImplicitGlobal(external_cfg, (*cb_global))) {
continue;
}
isc_throw (DhcpConfigError, "Config backend supplied unsupported global: " <<
(*cb_global)->getName() << " = " << (*cb_global)->getValue());
}
}
bool handleExplicitGlobal(SrvConfigPtr external_cfg, const data::StampedValuePtr& cb_global) {
bool was_handled = true;
try {
const std::string& name = cb_global->getName();
if (name == "decline-probation-period") {
external_cfg->setDeclinePeriod(cb_global->getSignedIntegerValue());
}
else if (name == "echo-client-id") {
external_cfg->setEchoClientId(cb_global->getValue() == "true" ? true : false);
} else {
was_handled = false;
}
} catch(const std::exception& ex) {
isc_throw (BadValue, "Invalid value:" << cb_global->getValue()
<< " explict global:" << cb_global->getName());
}
// @todo Fetching and merging the configuration falls under #99
// ConfigBackendDHCPv4Mgr& mgr = ConfigBackendDHCPv4Mgr::instance();
// Next we have to fetch the pieces we care about it and merge them
// probably in this order?
// globals
// option defs
// options
// shared networks
// subnets
return (was_handled);
}
bool handleImplicitGlobal(SrvConfigPtr external_cfg, const data::StampedValuePtr& cb_global) {
// @todo One day we convert it based on the type stored in StampedValue, but
// that day is not today. For now, if we find it in the global defaults use
// the element type there.
for (auto global_default : SimpleParser4::GLOBAL4_DEFAULTS) {
if (global_default.name_ == cb_global->getName()) {
ElementPtr element = cb_global->toElement(global_default.type_);
external_cfg->addConfiguredGlobal(cb_global->getName(), element);
return (true);
}
}
// We didn't find it in the default list, so is it an optional implicit?
const std::string& name = cb_global->getName();
if ((name == "renew-timer") ||
(name == "rebind-timer")) {
ElementPtr element = cb_global->toElement(Element::integer);
external_cfg->addConfiguredGlobal(cb_global->getName(), element);
return (true);
}
return (false);
}
}; // end of isc::dhcp namespace
}; // end of isc namespace
......@@ -5,6 +5,7 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <cc/data.h>
#include <cc/stamped_value.h>
#include <dhcpsrv/parsers/dhcp_parsers.h>
#include <exceptions/exceptions.h>
......@@ -59,6 +60,19 @@ configureDhcp4Server(Dhcpv4Srv&,
isc::data::ConstElementPtr config_set,
bool check_only = false);
/// @param Fetch and merge data from config backends into the staging config
///
/// If the given SrvConfig specifies one or more config backends it calls
/// @c databaseConfigConnect() to open connections to them, otherwise it
/// simply returns. Next it creates an external SrvConfig instance,
/// and populates with data it fetches from the config backends.
/// Finally, it merges this external config into the staging config.
///
/// @param srv_cfg server configuration that (may) specify the backends
/// should be merged
void
databaseConfigFetch(const SrvConfigPtr& srv_cfg);
/// @brief Attempts to connect to configured CB databases
///
/// First, this function will close all existing CB backends. It
......@@ -76,17 +90,52 @@ configureDhcp4Server(Dhcpv4Srv&,
bool
databaseConfigConnect(const SrvConfigPtr& srv_cfg);
/// @brief Fetch configuration from CB databases and merge it into the given configuration
/// @brief Adds globals fetched from config backend(s) to a SrvConfig instance
///
/// It will call @c databaseConfigConnect, passing in the given server configuration. If
/// that call results in open CB databases, the function will then proceed to fetch
/// configuration components from said databases and merge them into the given server
/// configuration.
/// Iterates over the given collection of global parameters and either uses them
/// to set explicit members of the given SrvConfig or to it's list of configured
/// (aka implicit) globals.
///
/// @param srv_cfg Server configuration into which database configuration should be merged
/// @param mutable_cfg parsed configuration from the configuration file plus default values (ignored)
void
databaseConfigFetch(const SrvConfigPtr& srv_cfg, isc::data::ElementPtr mutable_cfg);
/// @param external_cfg SrvConfig instance to update
/// @param cb_globals collection of global parameters supplied by configuration
/// backend
///
/// @throw DhcpConfigError if any of the globals is not recognized as a supported
/// value.
void addGlobalsToConfig(SrvConfigPtr external_cfg,
data::StampedValueCollection& cb_globals);
/// @brief Sets the appropriate member of SrvConfig from a config backend
/// global value
///
/// If the given global maps to a global parameter stored explicitly as member
/// of SrvConfig, then it's value is used to set said member.
///
/// @param external_cfg SrvConfig instance to update
/// @param cb_global global parameter supplied by configuration backend
///
/// @return True if the global mapped to an explicit member of SrvConfig,
/// false otherwise
///
/// @throw BadValue if the global's value is not the expected data type
bool handleExplicitGlobal(SrvConfigPtr external_cfg,
const data::StampedValuePtr& cb_global);
/// @brief Adds a config backend global value to a SrvConfig's list of
/// configured globals
///
/// The given global is converted to an Element of the appropriate type and
/// added to the SrvConfig's list of configured globals.
///
/// @param external_cfg SrvConfig instance to update
/// @param cb_global global parameter supplied by configuration backend
///
/// @return true if the global is recognized as a supported global, false
/// otherwise
///
/// @throw BadValue if the global's value is not the expected data type
bool handleImplicitGlobal(SrvConfigPtr external_cfg,
const data::StampedValuePtr& cb_global);
}; // end of isc::dhcp namespace
}; // end of isc namespace
......
......@@ -85,6 +85,7 @@ dhcp4_unittests_SOURCES += dhcp4_test_utils.cc dhcp4_test_utils.h
dhcp4_unittests_SOURCES += direct_client_unittest.cc
dhcp4_unittests_SOURCES += ctrl_dhcp4_srv_unittest.cc
dhcp4_unittests_SOURCES += classify_unittest.cc
dhcp4_unittests_SOURCES += config_backend_unittest.cc
dhcp4_unittests_SOURCES += config_parser_unittest.cc
dhcp4_unittests_SOURCES += fqdn_unittest.cc
dhcp4_unittests_SOURCES += marker_file.cc
......
This diff is collapsed.
......@@ -6397,7 +6397,6 @@ TEST_F(Dhcp4ParserTest, globalReservations) {
// Rather than disable these tests they are compiled out. This avoids them
// reporting as disbabled and thereby drawing attention to them.
#ifdef CONFIG_BACKEND
// This test verifies that configuration control with unsupported type fails
TEST_F(Dhcp4ParserTest, configControlInfoNoFactory) {
string config = PARSER_CONFIGS[6];
......@@ -6475,7 +6474,6 @@ TEST_F(Dhcp4ParserTest, serverTag) {
// Make sure a invalid server-tag fails to parse.
ASSERT_THROW(parseDHCP4(bad_tag), std::exception);
}
#endif // CONFIG_BACKEND
// Check whether it is possible to configure packet queue
TEST_F(Dhcp4ParserTest, dhcpQueueControl) {
......@@ -6679,7 +6677,7 @@ TEST_F(Dhcp4ParserTest, calculateTeeTimesInheritence) {
// Subnet 100 should use it's own explicit values.
ConstSubnet4Ptr subnet4 = subnets4->getBySubnetId(100);
ASSERT_TRUE(subnet4);
EXPECT_EQ(false, subnet4->getCalculateTeeTimes());
EXPECT_FALSE(subnet4->getCalculateTeeTimes());
EXPECT_TRUE(util::areDoublesEquivalent(0.45, subnet4->getT1Percent()));
EXPECT_TRUE(util::areDoublesEquivalent(0.65, subnet4->getT2Percent()));
......@@ -6693,7 +6691,7 @@ TEST_F(Dhcp4ParserTest, calculateTeeTimesInheritence) {
// Subnet 300 should use the global values.
subnet4 = subnets4->getBySubnetId(300);
ASSERT_TRUE(subnet4);
EXPECT_EQ(false, subnet4->getCalculateTeeTimes());
EXPECT_FALSE(subnet4->getCalculateTeeTimes());
EXPECT_TRUE(util::areDoublesEquivalent(0.5, subnet4->getT1Percent()));
EXPECT_TRUE(util::areDoublesEquivalent(0.875, subnet4->getT2Percent()));
}
......
// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2018-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
......@@ -55,5 +55,59 @@ StampedValue::getSignedIntegerValue() const {
return (0);
}
ElementPtr
StampedValue::toElement(Element::types elem_type) {
ElementPtr element;
switch(elem_type) {
case Element::string: {
element.reset(new StringElement(value_));
break;
}
case Element::integer: {
try {
int64_t int_value = boost::lexical_cast<int64_t>(value_);
element.reset(new IntElement(int_value));
} catch (const std::exception& ex) {
isc_throw(BadValue, "StampedValue::toElement: integer value expected for: "
<< name_ << ", value is: " << value_ );
}
break;
}
case Element::boolean: {
bool bool_value;
if (value_ == std::string("true")) {
bool_value = true;
} else if (value_ == std::string("false")) {
bool_value = false;
} else {
isc_throw(BadValue, "StampedValue::toElement: boolean value specified as "
<< name_ << ", value is: " << value_
<< ", expected true or false");
}
element.reset(new BoolElement(bool_value));
break;
}
case Element::real: {
try {
double dbl_value = boost::lexical_cast<double>(value_);
element.reset(new DoubleElement(dbl_value));
}
catch (const std::exception& ex) {
isc_throw(BadValue, "StampedValue::toElement: real number value expected for: "
<< name_ << ", value is: " << value_ );
}
break;
}
default:
isc_throw (BadValue, "StampedValue::toElement: unsupported element type "
<< elem_type << " for: " << name_);
break;
}
return (element);
}
} // end of namespace isc::data
} // end of namespace isc
......@@ -7,6 +7,7 @@
#ifndef STAMPED_VALUE_H
#define STAMPED_VALUE_H
#include <cc/data.h>
#include <cc/stamped_element.h>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/mem_fun.hpp>
......@@ -83,6 +84,18 @@ public:
/// @throw BadValue if the value can't be converted to an integer.
int64_t getSignedIntegerValue() const;
/// @brief Creates an Element with the appropriate value
///
/// @param etype type of Element to create
/// @todo If StampedValue is extended to contain the Element::type
/// this parameter can be done away with.
///
/// @return A pointer to the new Element
/// @throw BadValue if the current value is invalid for the
/// requested element type. InvalidOperation if the requested
/// type is unsupported.
ElementPtr toElement(const Element::types etype);
private:
/// @brief Name of the value.
......
......@@ -36,4 +36,79 @@ TEST(StampedValueTest, createFromInteger) {
EXPECT_EQ(5, signed_integer);
}
// Tests that Elements can be created from stamped values.
TEST(StampedValueTest, toElement) {
ElementPtr elem;
boost::scoped_ptr<StampedValue> value;
// Make sure we can create a StringElement.
ASSERT_NO_THROW(value.reset(new StampedValue("foo", "boo")));
ASSERT_NO_THROW(elem = value->toElement(Element::string));
ASSERT_EQ(Element::string, elem->getType());
ASSERT_EQ("boo", elem->stringValue());
// Make non-string types fail.
ASSERT_THROW(value->toElement(Element::integer), BadValue);
ASSERT_THROW(value->toElement(Element::boolean), BadValue);
ASSERT_THROW(value->toElement(Element::real), BadValue);
// Make sure we can create a IntElement.
ASSERT_NO_THROW(value.reset(new StampedValue("foo", "777")));
ASSERT_NO_THROW(elem = value->toElement(Element::integer));
ASSERT_EQ(Element::integer, elem->getType());
ASSERT_EQ(777, elem->intValue());
// String should work.
ASSERT_NO_THROW(elem = value->toElement(Element::string));
ASSERT_EQ("777", elem->stringValue());
// Real should work.
ASSERT_NO_THROW(elem = value->toElement(Element::real));
ASSERT_EQ(777.0, elem->doubleValue());
// Boolean will fail.
ASSERT_THROW(value->toElement(Element::boolean), BadValue);
// Make sure we can create a Boolean.
ASSERT_NO_THROW(value.reset(new StampedValue("foo", "true")));
ASSERT_NO_THROW(elem = value->toElement(Element::boolean));
ASSERT_EQ(Element::boolean, elem->getType());
ASSERT_TRUE(elem->boolValue());
ASSERT_NO_THROW(value.reset(new StampedValue("foo", "false")));
ASSERT_NO_THROW(elem = value->toElement(Element::boolean));
ASSERT_EQ(Element::boolean, elem->getType());
ASSERT_FALSE(elem->boolValue());
// String should work.
ASSERT_NO_THROW(elem = value->toElement(Element::string));
ASSERT_EQ("false", elem->stringValue());
// Make numerics should fail.
ASSERT_THROW(value->toElement(Element::integer), BadValue);
ASSERT_THROW(value->toElement(Element::real), BadValue);
// Make sure only lower case "true" and "false" works for Booleans.
ASSERT_NO_THROW(value.reset(new StampedValue("foo", "TRUE")));
ASSERT_THROW(value->toElement(Element::boolean), BadValue);
ASSERT_NO_THROW(value.reset(new StampedValue("foo", "FALSE")));
ASSERT_THROW(value->toElement(Element::boolean), BadValue);
ASSERT_NO_THROW(value.reset(new StampedValue("foo","nonsens")));
ASSERT_THROW(value->toElement(Element::boolean), BadValue);
// Make sure we can create a DoubleElement.
ASSERT_NO_THROW(value.reset(new StampedValue("foo", "45.0")));
ASSERT_NO_THROW(elem = value->toElement(Element::real));
ASSERT_EQ(Element::real, elem->getType());
ASSERT_EQ(45.0, elem->doubleValue());
// String should work.
ASSERT_NO_THROW(elem = value->toElement(Element::string));
ASSERT_EQ("45.0", elem->stringValue());
// Int and Boolean should fail.
ASSERT_THROW(value->toElement(Element::integer), BadValue);
ASSERT_THROW(value->toElement(Element::boolean), BadValue);
}
}
......@@ -34,7 +34,7 @@ public:
}
try {
db_type_ = connection_.getParameter("host");
host_ = connection_.getParameter("host");
} catch (...) {
host_ = "default_host";
}
......
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