]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#99,!197] CfgMgr updated to merge config from external source.
authorMarcin Siodelski <marcin@isc.org>
Thu, 10 Jan 2019 18:55:01 +0000 (19:55 +0100)
committerMarcin Siodelski <marcin@isc.org>
Mon, 14 Jan 2019 12:18:47 +0000 (07:18 -0500)
src/lib/dhcpsrv/cfgmgr.cc
src/lib/dhcpsrv/cfgmgr.h
src/lib/dhcpsrv/srv_config.cc
src/lib/dhcpsrv/srv_config.h
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
src/lib/dhcpsrv/tests/srv_config_unittest.cc

index 6d28ea4aed1047d096c1b034d15fe8f07119a86c..db59f1b1b9770712671bab6b5535d91ea2e5642d 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2019 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -80,6 +80,7 @@ CfgMgr::clear() {
         configuration_->removeStatistics();
     }
     configs_.clear();
+    external_configs_.clear();
     ensureCurrentAllocated();
 }
 
@@ -166,6 +167,42 @@ CfgMgr::getStagingCfg() {
     return (configs_.back());
 }
 
+SrvConfigPtr
+CfgMgr::createExternalCfg() {
+    uint32_t seq = 0;
+
+    if (!external_configs_.empty()) {
+        seq = external_configs_.rbegin()->second->getSequence() + 1;
+    }
+
+    SrvConfigPtr srv_config(new SrvConfig(seq));
+    external_configs_[seq] = srv_config;
+    return (srv_config);
+}
+
+void
+CfgMgr::mergeIntoStagingCfg(const uint32_t seq) {
+    mergeIntoCfg(getStagingCfg(), seq);
+}
+
+void
+CfgMgr::mergeIntoCurrentCfg(const uint32_t seq) {
+    mergeIntoCfg(getCurrentCfg(), seq);
+}
+
+void
+CfgMgr::mergeIntoCfg(const SrvConfigPtr& target_config, const uint32_t seq) {
+    auto source_config = external_configs_.find(seq);
+    if (source_config != external_configs_.end()) {
+        target_config->merge(*source_config->second);
+        external_configs_.erase(source_config);
+
+    } else {
+        isc_throw(BadValue, "the external configuration with the sequence number "
+                  "of " << seq << " was not found");
+    }
+}
+
 CfgMgr::CfgMgr()
     : datadir_(DHCP_DATA_DIR), d2_client_mgr_(), family_(AF_INET) {
     // DHCP_DATA_DIR must be set set with -DDHCP_DATA_DIR="..." in Makefile.am
index d03988ac56d1a4263ceab149453a4d7cc6f1d4c7..69bf61df39de33c53bfa18c38af7a1736032e672 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2019 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -138,9 +138,9 @@ public:
 
     /// @brief Removes current, staging and all previous configurations.
     ///
-    /// This function removes all configurations, including current and
-    /// staging configurations. It creates a new current configuration with
-    /// default settings.
+    /// This function removes all configurations, including current,
+    /// staging and external configurations. It creates a new current
+    /// configuration with default settings.
     ///
     /// This function is exception safe.
     void clear();
@@ -231,6 +231,42 @@ public:
     /// @return non-null pointer to the staging configuration.
     SrvConfigPtr getStagingCfg();
 
+    /// @brief Creates external configuration and returns pointer to it.
+    ///
+    /// External configurations are those that come from other sources than
+    /// from the configuration file, e.g. a database or a command. They
+    /// are created aside and merged into the staging or current configuration.
+    /// External configurations are accessed by their sequence numbers. The
+    /// sequence numbers are autogenerated when the external configuration
+    /// instance is created.
+    ///
+    /// @return non-null pointer to created external configuration.
+    SrvConfigPtr createExternalCfg();
+
+    /// @brief Merges external configuration with the given sequence number
+    /// into the staging configuration.
+    ///
+    /// After the merge, the source configuration is discarded from the
+    /// @c CfgMgr as it should not be used anymore.
+    ///
+    /// @param seq Source configuration sequence number.
+    ///
+    /// @throw BadValue if the external configuration with the given sequence
+    /// number doesn't exist.
+    void mergeIntoStagingCfg(const uint32_t seq);
+
+    /// @brief Merges external configuration with the given sequence number
+    /// into the current configuration.
+    ///
+    /// After the merge, the source configuration is discarded from the
+    /// @c CfgMgr as it should not be used anymore.
+    ///
+    /// @param seq Source configuration sequence number.
+    ///
+    /// @throw BadValue if the external configuration with the given sequence
+    /// number doesn't exist.
+    void mergeIntoCurrentCfg(const uint32_t seq);
+
     //@}
 
     /// @brief Sets address family (AF_INET or AF_INET6)
@@ -267,6 +303,15 @@ private:
     /// default current configuration.
     void ensureCurrentAllocated();
 
+
+    /// @brief Merges external configuration with the given sequence number
+    /// into the specified configuration.
+    ///
+    /// @param target_config Pointer to the configuration into which the
+    /// external configuration should be merged.
+    /// @param seq Source configuration sequence number.
+    void mergeIntoCfg(const SrvConfigPtr& taget_config, const uint32_t seq);
+
     /// @brief directory where data files (e.g. server-id) are stored
     std::string datadir_;
 
@@ -289,6 +334,17 @@ private:
     SrvConfigList configs_;
     //@}
 
+    /// @name Map of external configurations.
+    ///
+    //@{
+    /// @brief Server configuration map type.
+    typedef std::map<uint32_t, SrvConfigPtr> SrvConfigMap;
+
+    /// @brief Map of external configurations with sequence numbers used
+    /// as keys.
+    SrvConfigMap external_configs_;
+    //@}
+
     /// @brief Address family.
     uint16_t family_;
 };
index 7e61f2c0c0814295dddabe2ba450fd58bf8ea9f4..b676687af1ef6019deb50512c63749036579fd90 100644 (file)
@@ -5,6 +5,7 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 #include <config.h>
+#include <exceptions/exceptions.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/srv_config.h>
 #include <dhcpsrv/lease_mgr_factory.h>
@@ -161,9 +162,17 @@ SrvConfig::merge(const ConfigBase& other) {
     ConfigBase::merge(other);
 
     try {
+        /// @todo merge other parts of the configuration here.
+
         const SrvConfig& other_srv_config = dynamic_cast<const SrvConfig&>(other);
+        cfg_subnets4_->merge(*other_srv_config.getCfgSubnets4());
+
+        /// @todo merge other parts of the configuration here.
 
     } catch (const std::bad_cast&) {
+        isc_throw(InvalidOperation, "internal server error: must use derivation"
+                  " of the SrvConfig as an argument of the call to"
+                  " SrvConfig::merge()");
     }
 }
 
index 16129c712c97311cc49607c4b4083c7c82e99405..a2b4c76611d2dad190ba3c4b50307e31110b5cf1 100644 (file)
@@ -448,8 +448,6 @@ public:
         return (!equals(other));
     }
 
-    virtual void merge(const ConfigBase& other);
-
     /// @brief Equality operator.
     ///
     /// It ignores the configuration sequence number when checking for
@@ -476,6 +474,47 @@ public:
 
     //@}
 
+    /// @brief Merges the configuration specified as a parameter into
+    /// this configuration.
+    ///
+    /// This method is used when two or more configurations held in the
+    /// @c SrvConfig objects need to be combined into a single configuration.
+    /// Specifically, when the configuration backend is used, the part of
+    /// the server configuration comes from the configuration file and
+    /// stored in the staging configuration. The other part of the
+    /// configuration comes from the database. The configuration fetched
+    /// from the database is stored in a separate @c SrvConfig instance
+    /// and then merged into the staging configuration prior to commiting
+    /// it.
+    ///
+    /// The merging strategy depends on the underlying data being merged.
+    /// For example: subnets are merged using the algorithm implemented
+    /// in the @c CfgSubnets4. Other data structures are merged using the
+    /// algorithms implemented in their respective configuration
+    /// containers.
+    ///
+    /// The general rule is that the configuration data from the @c other
+    /// object replaces configuration data held in this object instance.
+    /// The data that do not overlap between the two objects is simply
+    /// inserted into this configuration.
+    ///
+    /// @note The call to @c merge may modify the data in the @c other
+    /// object. Therefore, the caller must not rely on the data held
+    /// in the @c other object after the call to @c merge. Also, the
+    /// data held in @c other must not be modified after the call to
+    /// @c merge because it may affect the merged configuration.
+    ///
+    /// The @c other parameter must be a @c SrvConfig or its derivation.
+    ///
+    /// Currently, the following parts of the configuration are merged:
+    /// - IPv4 subnets
+    ///
+    /// @todo Add support for merging other configuration elements.
+    ///
+    /// @param other An object holding the configuration to be merged
+    /// into this configuration.
+    virtual void merge(const ConfigBase& other);
+
     /// @brief Updates statistics.
     ///
     /// This method calls appropriate methods in child objects that update
index dd76af451171527af3e89ad3f8a25b05a4e8e025..61f9db84bf421b80104c576b57ac4523d838b82d 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-2019 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -662,6 +662,145 @@ TEST_F(CfgMgrTest, clearStats6) {
     EXPECT_FALSE(stats_mgr.getObservation("subnet[123].assigned-pds"));
 }
 
+// This test verifies that the external configuration can be merged into
+// the staging configuration via CfgMgr.
+TEST_F(CfgMgrTest, mergeIntoStagingCfg) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    // Create first external configuration.
+    SrvConfigPtr ext_cfg1;
+    ASSERT_NO_THROW(ext_cfg1 = cfg_mgr.createExternalCfg());
+    ASSERT_TRUE(ext_cfg1);
+    // It should pick the first available sequence number.
+    EXPECT_EQ(0, ext_cfg1->getSequence());
+
+    // Create second external configuration.
+    SrvConfigPtr ext_cfg2;
+    ASSERT_NO_THROW(ext_cfg2 = cfg_mgr.createExternalCfg());
+    ASSERT_TRUE(ext_cfg2);
+    // It should pick the next available sequence number.
+    EXPECT_EQ(1, ext_cfg2->getSequence());
+
+    // Those must be two separate instances.
+    ASSERT_FALSE(ext_cfg1 == ext_cfg2);
+
+    // Add a subnet which will be merged from first configuration.
+    Subnet4Ptr subnet1(new Subnet4(IOAddress("192.1.2.0"), 24, 1, 2, 3, 123));
+    ext_cfg1->getCfgSubnets4()->add(subnet1);
+
+    // Add a subnet which will be merged from the second configuration.
+    Subnet4Ptr subnet2(new Subnet4(IOAddress("192.1.3.0"), 24, 1, 2, 3, 124));
+    ext_cfg2->getCfgSubnets4()->add(subnet2);
+
+    // Merge first configuration.
+    ASSERT_NO_THROW(cfg_mgr.mergeIntoStagingCfg(ext_cfg1->getSequence()));
+    // Second attempt should fail because the configuration is discarded after
+    // the merge.
+    ASSERT_THROW(cfg_mgr.mergeIntoStagingCfg(ext_cfg1->getSequence()), BadValue);
+
+    // Check that the subnet from first configuration has been merged but not
+    // from the second configuration.
+    ASSERT_TRUE(cfg_mgr.getStagingCfg()->getCfgSubnets4()->getBySubnetId(123));
+    ASSERT_FALSE(cfg_mgr.getStagingCfg()->getCfgSubnets4()->getBySubnetId(124));
+
+    // Create another configuration instance to check what sequence it would
+    // pick. It should pick the first available one.
+    SrvConfigPtr ext_cfg3;
+    ASSERT_NO_THROW(ext_cfg3 = cfg_mgr.createExternalCfg());
+    ASSERT_TRUE(ext_cfg3);
+    EXPECT_EQ(2, ext_cfg3->getSequence());
+
+    // Merge the second and third (empty) configuration.
+    ASSERT_NO_THROW(cfg_mgr.mergeIntoStagingCfg(ext_cfg2->getSequence()));
+    ASSERT_NO_THROW(cfg_mgr.mergeIntoStagingCfg(ext_cfg3->getSequence()));
+
+    // Make sure that both subnets have been merged.
+    ASSERT_TRUE(cfg_mgr.getStagingCfg()->getCfgSubnets4()->getBySubnetId(123));
+    ASSERT_TRUE(cfg_mgr.getStagingCfg()->getCfgSubnets4()->getBySubnetId(124));
+
+    // The next configuration instance should reset the sequence to 0 because
+    // there are no other configurations in CfgMgr.
+    SrvConfigPtr ext_cfg4;
+    ASSERT_NO_THROW(ext_cfg4 = cfg_mgr.createExternalCfg());
+    ASSERT_TRUE(ext_cfg4);
+    EXPECT_EQ(0, ext_cfg4->getSequence());
+
+    // Try to commit the staging configuration.
+    ASSERT_NO_THROW(cfg_mgr.commit());
+
+    // Make sure that both subnets are present in the current configuration.
+    EXPECT_TRUE(cfg_mgr.getCurrentCfg()->getCfgSubnets4()->getBySubnetId(123));
+    EXPECT_TRUE(cfg_mgr.getCurrentCfg()->getCfgSubnets4()->getBySubnetId(124));
+
+    // The staging configuration should not include them.
+    EXPECT_FALSE(cfg_mgr.getStagingCfg()->getCfgSubnets4()->getBySubnetId(123));
+    EXPECT_FALSE(cfg_mgr.getStagingCfg()->getCfgSubnets4()->getBySubnetId(124));
+}
+
+// This test verifies that the external configuration can be merged into
+// the current configuration via CfgMgr.
+TEST_F(CfgMgrTest, mergeIntoCurrentCfg) {
+    CfgMgr& cfg_mgr = CfgMgr::instance();
+
+    // Create first external configuration.
+    SrvConfigPtr ext_cfg1;
+    ASSERT_NO_THROW(ext_cfg1 = cfg_mgr.createExternalCfg());
+    ASSERT_TRUE(ext_cfg1);
+    // It should pick the first available sequence number.
+    EXPECT_EQ(0, ext_cfg1->getSequence());
+
+    // Create second external configuration.
+    SrvConfigPtr ext_cfg2;
+    ASSERT_NO_THROW(ext_cfg2 = cfg_mgr.createExternalCfg());
+    ASSERT_TRUE(ext_cfg2);
+    // It should pick the next available sequence number.
+    EXPECT_EQ(1, ext_cfg2->getSequence());
+
+    // Those must be two separate instances.
+    ASSERT_FALSE(ext_cfg1 == ext_cfg2);
+
+    // Add a subnet which will be merged from first configuration.
+    Subnet4Ptr subnet1(new Subnet4(IOAddress("192.1.2.0"), 24, 1, 2, 3, 123));
+    ext_cfg1->getCfgSubnets4()->add(subnet1);
+
+    // Add a subnet which will be merged from the second configuration.
+    Subnet4Ptr subnet2(new Subnet4(IOAddress("192.1.3.0"), 24, 1, 2, 3, 124));
+    ext_cfg2->getCfgSubnets4()->add(subnet2);
+
+    // Merge first configuration.
+    ASSERT_NO_THROW(cfg_mgr.mergeIntoCurrentCfg(ext_cfg1->getSequence()));
+    // Second attempt should fail because the configuration is discarded after
+    // the merge.
+    ASSERT_THROW(cfg_mgr.mergeIntoCurrentCfg(ext_cfg1->getSequence()), BadValue);
+
+    // Check that the subnet from first configuration has been merged but not
+    // from the second configuration.
+    ASSERT_TRUE(cfg_mgr.getCurrentCfg()->getCfgSubnets4()->getBySubnetId(123));
+    ASSERT_FALSE(cfg_mgr.getCurrentCfg()->getCfgSubnets4()->getBySubnetId(124));
+
+    // Create another configuration instance to check what sequence it would
+    // pick. It should pick the first available one.
+    SrvConfigPtr ext_cfg3;
+    ASSERT_NO_THROW(ext_cfg3 = cfg_mgr.createExternalCfg());
+    ASSERT_TRUE(ext_cfg3);
+    EXPECT_EQ(2, ext_cfg3->getSequence());
+
+    // Merge the second and third (empty) configuration.
+    ASSERT_NO_THROW(cfg_mgr.mergeIntoCurrentCfg(ext_cfg2->getSequence()));
+    ASSERT_NO_THROW(cfg_mgr.mergeIntoCurrentCfg(ext_cfg3->getSequence()));
+
+    // Make sure that both subnets have been merged.
+    ASSERT_TRUE(cfg_mgr.getCurrentCfg()->getCfgSubnets4()->getBySubnetId(123));
+    ASSERT_TRUE(cfg_mgr.getCurrentCfg()->getCfgSubnets4()->getBySubnetId(124));
+
+    // The next configuration instance should reset the sequence to 0 because
+    // there are no other configurations in CfgMgr.
+    SrvConfigPtr ext_cfg4;
+    ASSERT_NO_THROW(ext_cfg4 = cfg_mgr.createExternalCfg());
+    ASSERT_TRUE(ext_cfg4);
+    EXPECT_EQ(0, ext_cfg4->getSequence());
+}
+
 /// @todo Add unit-tests for testing:
 /// - addActiveIface() with invalid interface name
 /// - addActiveIface() with the same interface twice
index 1b7f56f90f0eb1db33c6c44a48802687c79e61ce..9ea12d9d3d46a4aa0a00523c16994c897206ee85 100644 (file)
@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2018 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-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
@@ -25,6 +25,13 @@ using namespace isc::process;
 
 namespace {
 
+/// @brief Derivation of the @c ConfigBase not being @c SrvConfig.
+///
+/// This is used to verify that the appropriate error is returned
+/// when other derivation of the @c ConfigBase than @c SrvConfig
+/// is used.
+class NonSrvConfig : public ConfigBase { };
+
 /// @brief Number of IPv4 and IPv6 subnets to be created for a test.
 const int TEST_SUBNETS_NUM = 3;
 
@@ -974,4 +981,13 @@ TEST_F(SrvConfigTest, unparseConfigControlInfo6) {
     EXPECT_TRUE(info_elem->equals(*check));
 }
 
+// Verifies that exception is thrown when instead of SrvConfig
+// another derivation of ConfigBase is used in the call to
+// merge.
+TEST_F(SrvConfigTest, mergeBadCast) {
+    SrvConfig srv_config;
+    NonSrvConfig non_srv_config;
+    ASSERT_THROW(srv_config.merge(non_srv_config), isc::InvalidOperation);
+}
+
 } // end of anonymous namespace