From: Thomas Markwalder Date: Thu, 24 Jan 2019 15:16:19 +0000 (-0500) Subject: [#101,!202] Interrim commit - v4 fetch and merge functional X-Git-Tag: 429-Updated-StampedValue-to-support-reals_base~33^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=dc74694de3862a3a589a5f1c12ce2d8b4ce5ae6a;p=thirdparty%2Fkea.git [#101,!202] Interrim commit - v4 fetch and merge functional Server fetches config, populates external SrvConfig and invokes merge. libdhcpsrv still lacks merging of globals,opt defs,options, and shared networks. src/bin/dhcp4/json_config_parser.* configureDhcp4Server() - restored call to databaseConfigFetch() databaseConfigFetch(const SrvConfigPtr& srv_cfg) - completed implementation, now fetches external config and invokes merge addGlobalsToConfig() handleExplicitGlobal() handleImplicitGlobal() - new functions for populating external config globals with backend globals src/bin/dhcp4/tests config_backend_unittest.cc - new file that tests config fetch and merge src/lib/cc/stamped_value.* StampedValue::toElement(Element::types elem_type) - new method for creating Elements from StampedValues src/lib/cc/tests/stamped_value_unittest.cc TEST(StampedValueTest, toElement) - new test src/lib/dhcpsrv/testutils/test_config_backend.h TestConfigBackend() - fixed host_ assignment --- diff --git a/src/bin/dhcp4/json_config_parser.cc b/src/bin/dhcp4/json_config_parser.cc index 4af7d4a336..802508d9c1 100644 --- a/src/bin/dhcp4/json_config_parser.cc +++ b/src/bin/dhcp4/json_config_parser.cc @@ -8,6 +8,8 @@ #include #include +#include +#include #include #include #include @@ -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,62 @@ 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; + } + + // 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. @@ -678,25 +736,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(); + + 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 diff --git a/src/bin/dhcp4/json_config_parser.h b/src/bin/dhcp4/json_config_parser.h index de46976409..f26f404a8e 100644 --- a/src/bin/dhcp4/json_config_parser.h +++ b/src/bin/dhcp4/json_config_parser.h @@ -5,6 +5,7 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include +#include #include #include @@ -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 diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am index 9e0221d3b5..91a63f75d7 100644 --- a/src/bin/dhcp4/tests/Makefile.am +++ b/src/bin/dhcp4/tests/Makefile.am @@ -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 diff --git a/src/bin/dhcp4/tests/config_backend_unittest.cc b/src/bin/dhcp4/tests/config_backend_unittest.cc new file mode 100644 index 0000000000..9f7246b550 --- /dev/null +++ b/src/bin/dhcp4/tests/config_backend_unittest.cc @@ -0,0 +1,406 @@ +// 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 + +#include +#include + +#include +#include +#include +#include +#include +//#include +//#include +#include +#include +#include +#include + +#include "dhcp4_test_utils.h" +#include "get_config_unittest.h" + +#include +#include + +#include +#include +#include +#include + +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 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); +} + +} diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc index 31995812af..489d8eec35 100644 --- a/src/bin/dhcp4/tests/config_parser_unittest.cc +++ b/src/bin/dhcp4/tests/config_parser_unittest.cc @@ -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())); } diff --git a/src/lib/cc/stamped_value.cc b/src/lib/cc/stamped_value.cc index 38ab82b615..9a3aed125d 100644 --- a/src/lib/cc/stamped_value.cc +++ b/src/lib/cc/stamped_value.cc @@ -1,4 +1,4 @@ -// 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 { + int int_value = boost::lexical_cast(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(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 diff --git a/src/lib/cc/stamped_value.h b/src/lib/cc/stamped_value.h index 7bb6ba5ab2..eda2f94155 100644 --- a/src/lib/cc/stamped_value.h +++ b/src/lib/cc/stamped_value.h @@ -7,6 +7,7 @@ #ifndef STAMPED_VALUE_H #define STAMPED_VALUE_H +#include #include #include #include @@ -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. diff --git a/src/lib/cc/tests/stamped_value_unittest.cc b/src/lib/cc/tests/stamped_value_unittest.cc index 4419a5f7f8..ad2581dcba 100644 --- a/src/lib/cc/tests/stamped_value_unittest.cc +++ b/src/lib/cc/tests/stamped_value_unittest.cc @@ -36,4 +36,71 @@ 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 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); +} + } diff --git a/src/lib/dhcpsrv/testutils/test_config_backend.h b/src/lib/dhcpsrv/testutils/test_config_backend.h index b82cc00b6a..45ae8154c1 100644 --- a/src/lib/dhcpsrv/testutils/test_config_backend.h +++ b/src/lib/dhcpsrv/testutils/test_config_backend.h @@ -34,7 +34,7 @@ public: } try { - db_type_ = connection_.getParameter("host"); + host_ = connection_.getParameter("host"); } catch (...) { host_ = "default_host"; }