]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#101,!202] Interrim commit - v4 fetch and merge functional
authorThomas Markwalder <tmark@isc.org>
Thu, 24 Jan 2019 15:16:19 +0000 (10:16 -0500)
committerThomas Markwalder <tmark@isc.org>
Thu, 24 Jan 2019 15:16:19 +0000 (10:16 -0500)
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

src/bin/dhcp4/json_config_parser.cc
src/bin/dhcp4/json_config_parser.h
src/bin/dhcp4/tests/Makefile.am
src/bin/dhcp4/tests/config_backend_unittest.cc [new file with mode: 0644]
src/bin/dhcp4/tests/config_parser_unittest.cc
src/lib/cc/stamped_value.cc
src/lib/cc/stamped_value.h
src/lib/cc/tests/stamped_value_unittest.cc
src/lib/dhcpsrv/testutils/test_config_backend.h

index 4af7d4a336bc76442bd1ae90a786964af3592092..802508d9c1b3a4ebb931bbf33935531ccf1b5c2c 100644 (file)
@@ -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,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<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
index de469764093f12e9f33d22f405bb48997af02292..f26f404a8e5bffed5c45056c67dca566a3b4450c 100644 (file)
@@ -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
index 9e0221d3b52b5aa07c3be794e8dbc60a285e8fa2..91a63f75d7fffedfdac4327826ed1fa56e77b134 100644 (file)
@@ -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 (file)
index 0000000..9f7246b
--- /dev/null
@@ -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 <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);
+}
+
+}
index 31995812afa079810b565b391dd987c5eb144778..489d8eec35a134f8f862126343a9b438f2fcd6ae 100644 (file)
@@ -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()));
 }
index 38ab82b615ed5f08443a79ac982ca1b4c26070ed..9a3aed125d4a1aee6d891796674397b127861767 100644 (file)
@@ -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<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
index 7bb6ba5ab22d3ed0d8f6d0419b2d79e2bc6615bf..eda2f941551a10b36e1d2724022ac75df87f6a5e 100644 (file)
@@ -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.
index 4419a5f7f8f73ede02561f3f315e9e70ab621577..ad2581dcba6ce73bbdbb84945dd95e5561c22cf5 100644 (file)
@@ -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<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);
+}
+
 }
index b82cc00b6a77b5c29bd196831b5ccb2eccbb4247..45ae8154c12806a9f1c566b6aaa06d24afaa1ab5 100644 (file)
@@ -34,7 +34,7 @@ public:
         }
 
         try {
-            db_type_ = connection_.getParameter("host");
+            host_ = connection_.getParameter("host");
         } catch (...) {
             host_ = "default_host";
         }