#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>
using namespace isc::hooks;
using namespace isc::process;
using namespace isc::config;
+using namespace isc::db;
namespace {
}
}
+
isc::data::ConstElementPtr
configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set,
bool check_only) {
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());
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;
+ }
+
+ // 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.
+ // Probably a good place for a log message
+ CfgMgr::instance().mergeIntoStagingCfg(external_cfg->getSequence());
+}
+
bool databaseConfigConnect(const SrvConfigPtr& srv_cfg) {
// We need to get rid of any existing backends. These would be any
// opened by previous configuration cycle.
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
// 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>
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
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
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
--- /dev/null
+// 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 <dhcp4/dhcp4_srv.h>
+#include <dhcp4/ctrl_dhcp4_srv.h>
+#include <dhcp4/json_config_parser.h>
+//#include <dhcp/option_custom.h>
+//#include <dhcp/option_int.h>
+#include <dhcpsrv/subnet.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_subnets4.h>
+#include <dhcpsrv/testutils/test_config_backend_dhcp4.h>
+
+#include "dhcp4_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;
+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 Dhcp4CBTest : public ::testing::Test {
+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 TestConfigBackendDHCPv4(params));
+
+ params[std::string("host")] = std::string("db2");
+ db2_.reset(new TestConfigBackendDHCPv4(params));
+
+ ConfigBackendDHCPv4Mgr::instance().registerBackendFactory("memfile",
+ [this](const db::DatabaseConnection::ParameterMap& params)
+ -> dhcp::ConfigBackendDHCPv4Ptr {
+ 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 on that does not prexist.
+ return (TestConfigBackendDHCPv4Ptr(new TestConfigBackendDHCPv4(params)));
+ });
+ }
+
+public:
+
+ /// Constructor
+ Dhcp4CBTest()
+ : 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 ControlledDhcpv4Srv(0));
+
+ // Create fresh context.
+ resetConfiguration();
+ }
+
+ /// Destructor
+ ~Dhcp4CBTest() {
+ resetConfiguration();
+ };
+
+ /// @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 = parseDHCP4(config, true);
+ } catch(const std::exception& ex) {
+ ADD_FAILURE() << "parseDHCP4 failed: " << ex.what();
+ }
+
+ ConstElementPtr status;
+ ASSERT_NO_THROW(status = configureDhcp4Server(*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);
+ }
+ }
+ }
+
+ /// @brief Reset configuration database.
+ ///
+ /// This function resets configuration data base by
+ /// removing all subnets and option-data. Reset must
+ /// be performed after each test to make sure that
+ /// contents of the database do not affect result of
+ /// subsequent tests.
+ void resetConfiguration() {
+ string config =
+ "{ \n"
+ "\"hooks-libraries\": [ ], \n"
+ "\"valid-lifetime\": 4000, \n"
+ "\"subnet4\": [ ], \n"
+ "\"dhcp-ddns\": { \"enable-updates\" : false }, \n"
+ "\"option-def\": [ ], \n"
+ "\"option-data\": [ ] }\n";
+ configure(config, CONTROL_RESULT_SUCCESS, "");
+ CfgMgr::instance().clear();
+ }
+
+ /// @brief Tests that a given global is in the staged configured globals
+ ///
+ /// @param name name of the global parameter
+ /// @param exp_value expected string value of the global parameter
+ /// @param exp_type expected Element type of the global global parameter
+ void checkConfiguredGlobal(const std::string &name,
+ const std::string exp_value,
+ const Element::types& exp_type) {
+ SrvConfigPtr staging_cfg = CfgMgr::instance().getStagingCfg();
+ ConstElementPtr globals = staging_cfg->getConfiguredGlobals();
+ ConstElementPtr found_global = globals->get(name);
+ ASSERT_TRUE(found_global) << "expected global: "
+ << name << " not found";
+
+ ASSERT_EQ(exp_type, found_global->getType())
+ << "global" << name << "is wrong type";
+
+ switch (exp_type) {
+ case Element::integer:
+ case Element::real:
+ case Element::boolean:
+ ASSERT_EQ(exp_value, found_global->str())
+ << "expected global: " << name << " has wrong value";
+ break;
+ case Element::string:
+ // We do it this way to avoid the embedded quotes
+ ASSERT_EQ(exp_value, found_global->stringValue())
+ << "expected global: " << name << " has wrong value";
+ break;
+ default:
+ ADD_FAILURE() << "unsupported global type, test broken?";
+ return;
+ }
+
+ }
+
+ /// @brief Tests that a given global is in the staged configured globals
+ ///
+ /// @param exp_global StampedValue representing the global value to verify
+ /// @param exp_type expected Element type of the global global parameter -
+ ///
+ /// @todo At the point in time StampedVlaue carries type, exp_type should be
+ /// replaced with exp_global->getType()
+ void checkConfiguredGlobal(StampedValuePtr& exp_global, const Element::types& exp_type ) {
+ checkConfiguredGlobal(exp_global->getName(), exp_global->getValue(), exp_type);
+ }
+
+ boost::scoped_ptr<Dhcpv4Srv> srv_; ///< DHCP4 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
+
+ TestConfigBackendDHCPv4Ptr db1_; ///< First configuration backend instance
+ TestConfigBackendDHCPv4Ptr db2_; ///< Second configuration backend instance
+};
+
+// This test verifies that externally configured globals are
+// merged correctly into staging configuration.
+TEST_F(Dhcp4CBTest, DISABLED_mergeGlobals) {
+ string base_config =
+ "{ \n"
+ " \"interfaces-config\": { \n"
+ " \"interfaces\": [\"*\" ] \n"
+ " }, \n"
+ " \"echo-client-id\": true, \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:
+ // @todo StampedValue is going to be extended to hold type.
+ StampedValuePtr serverHostname(new StampedValue("server-hostname", "isc.example.org"));
+ StampedValuePtr declinePeriod(new StampedValue("decline-probation-period", "86400"));
+ StampedValuePtr calcTeeTimes(new StampedValue("calculate-tee-times", "false"));
+ StampedValuePtr t2Percent(new StampedValue("t2-percent", "0.75"));
+ StampedValuePtr renewTimer(new StampedValue("renew-timer", "500"));
+
+ // Let's add all of the globals to the second backend. This will verify
+ // we find them there.
+ db2_->createUpdateGlobalParameter4(ServerSelector::ALL(), serverHostname);
+ db2_->createUpdateGlobalParameter4(ServerSelector::ALL(), declinePeriod);
+ db2_->createUpdateGlobalParameter4(ServerSelector::ALL(), calcTeeTimes);
+ db2_->createUpdateGlobalParameter4(ServerSelector::ALL(), t2Percent);
+ db2_->createUpdateGlobalParameter4(ServerSelector::ALL(), renewTimer);
+
+ // 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 an explicit member that should come from JSON.
+ EXPECT_TRUE(staging_cfg->getEchoClientId());
+
+ // decline-probation-periodis 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("valid-lifetime", "1000", Element::integer));
+ ASSERT_NO_FATAL_FAILURE(checkConfiguredGlobal("rebind-timer", "800", Element::integer));
+
+ // Verify that the implicit globals from the backend are there.
+ ASSERT_NO_FATAL_FAILURE(checkConfiguredGlobal(serverHostname, Element::string));
+ ASSERT_NO_FATAL_FAILURE(checkConfiguredGlobal(calcTeeTimes, Element::boolean));
+ ASSERT_NO_FATAL_FAILURE(checkConfiguredGlobal(t2Percent, Element::real));
+ ASSERT_NO_FATAL_FAILURE(checkConfiguredGlobal(renewTimer, Element::integer));
+}
+
+// This test verifies that externally configured shared-networks are
+// merged correctly into staging configuration.
+TEST_F(Dhcp4CBTest, DISABLED_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
+ SharedNetwork4Ptr network1(new SharedNetwork4("one"));
+ SharedNetwork4Ptr network3(new SharedNetwork4("three"));
+
+ // Add network1 to db1 and network3 to db2
+ db1_->createUpdateSharedNetwork4(ServerSelector::ALL(), network1);
+ db2_->createUpdateSharedNetwork4(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();
+ SharedNetwork4Ptr 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, since it is
+ // first found, first used?
+ staged_network = networks->getByName("three");
+ ASSERT_FALSE(staged_network);
+}
+
+// This test verifies that externally configured subnets are
+// merged correctly into staging configuration.
+TEST_F(Dhcp4CBTest, 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"
+ " \"subnet4\": [ \n"
+ " { \n"
+ " \"id\": 2,\n"
+ " \"subnet\": \"192.0.3.0/24\" \n"
+ " } ]\n"
+ "} \n";
+
+ extractConfig(base_config);
+
+ // Make a few subnets
+ Subnet4Ptr subnet1(new Subnet4(IOAddress("192.0.2.0"), 26, 1, 2, 3, SubnetID(1)));
+ Subnet4Ptr subnet3(new Subnet4(IOAddress("192.0.4.0"), 26, 1, 2, 3, SubnetID(3)));
+
+ // Add subnet1 to db1 and subnet3 to db2
+ db1_->createUpdateSubnet4(ServerSelector::ALL(), subnet1);
+ db2_->createUpdateSubnet4(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();
+
+ CfgSubnets4Ptr subnets = staging_cfg->getCfgSubnets4();
+ Subnet4Ptr 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);
+}
+
+}
// 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];
// 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) {
// 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()));
// 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
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 {
+ int int_value = boost::lexical_cast<int>(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
#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>
/// @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.
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 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);
+}
+
}
}
try {
- db_type_ = connection_.getParameter("host");
+ host_ = connection_.getParameter("host");
} catch (...) {
host_ = "default_host";
}