From: Marcin Siodelski Date: Thu, 10 Jan 2019 18:55:01 +0000 (+0100) Subject: [#99,!197] CfgMgr updated to merge config from external source. X-Git-Tag: 100-implement-test-config-backend-dhcp6_base~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=a022cca324c1184eb8a813f8996b4444c69dbc14;p=thirdparty%2Fkea.git [#99,!197] CfgMgr updated to merge config from external source. --- diff --git a/src/lib/dhcpsrv/cfgmgr.cc b/src/lib/dhcpsrv/cfgmgr.cc index 6d28ea4aed..db59f1b1b9 100644 --- a/src/lib/dhcpsrv/cfgmgr.cc +++ b/src/lib/dhcpsrv/cfgmgr.cc @@ -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 diff --git a/src/lib/dhcpsrv/cfgmgr.h b/src/lib/dhcpsrv/cfgmgr.h index d03988ac56..69bf61df39 100644 --- a/src/lib/dhcpsrv/cfgmgr.h +++ b/src/lib/dhcpsrv/cfgmgr.h @@ -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 SrvConfigMap; + + /// @brief Map of external configurations with sequence numbers used + /// as keys. + SrvConfigMap external_configs_; + //@} + /// @brief Address family. uint16_t family_; }; diff --git a/src/lib/dhcpsrv/srv_config.cc b/src/lib/dhcpsrv/srv_config.cc index 7e61f2c0c0..b676687af1 100644 --- a/src/lib/dhcpsrv/srv_config.cc +++ b/src/lib/dhcpsrv/srv_config.cc @@ -5,6 +5,7 @@ // file, You can obtain one at http://mozilla.org/MPL/2.0/. #include +#include #include #include #include @@ -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(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()"); } } diff --git a/src/lib/dhcpsrv/srv_config.h b/src/lib/dhcpsrv/srv_config.h index 16129c712c..a2b4c76611 100644 --- a/src/lib/dhcpsrv/srv_config.h +++ b/src/lib/dhcpsrv/srv_config.h @@ -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 diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc index dd76af4511..61f9db84bf 100644 --- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc @@ -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 diff --git a/src/lib/dhcpsrv/tests/srv_config_unittest.cc b/src/lib/dhcpsrv/tests/srv_config_unittest.cc index 1b7f56f90f..9ea12d9d3d 100644 --- a/src/lib/dhcpsrv/tests/srv_config_unittest.cc +++ b/src/lib/dhcpsrv/tests/srv_config_unittest.cc @@ -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