diff options
author | Thomas Markwalder <tmark@isc.org> | 2019-04-16 21:42:08 +0200 |
---|---|---|
committer | Thomas Markwalder <tmark@isc.org> | 2019-04-18 16:52:40 +0200 |
commit | ee02fde1f0a4e7d1ba599a20c1b6be86b4f5f9ee (patch) | |
tree | 06171cf49535286d6e2f58eeab7cf2bad999c5e0 /src/lib/dhcpsrv | |
parent | [397-cb-implement-mysqlconfigbackenddhcpv6] Added ChangeLog entry (diff) | |
download | kea-ee02fde1f0a4e7d1ba599a20c1b6be86b4f5f9ee.tar.xz kea-ee02fde1f0a4e7d1ba599a20c1b6be86b4f5f9ee.zip |
[#566,!304] kea-dhcp6 now removes objects deleted from config backend
src/lib/dhcpsrv/cb_ctl_dhcp6.cc
CBControlDHCPv6::databaseConfigApply() - revamped to
delete objects based on DELETE audit entries
src/lib/dhcpsrv/unittests
Renamed cb_ctl_dhcp_unittest.cc to cb_ctl_dhcp4_unittest.cc
cb_ctl_dhcp6_unittest.cc - new file
Added proposed ChangeLog
Diffstat (limited to 'src/lib/dhcpsrv')
-rw-r--r-- | src/lib/dhcpsrv/cb_ctl_dhcp6.cc | 103 | ||||
-rw-r--r-- | src/lib/dhcpsrv/tests/Makefile.am | 3 | ||||
-rw-r--r-- | src/lib/dhcpsrv/tests/cb_ctl_dhcp4_unittest.cc (renamed from src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc) | 0 | ||||
-rw-r--r-- | src/lib/dhcpsrv/tests/cb_ctl_dhcp6_unittest.cc | 764 |
4 files changed, 868 insertions, 2 deletions
diff --git a/src/lib/dhcpsrv/cb_ctl_dhcp6.cc b/src/lib/dhcpsrv/cb_ctl_dhcp6.cc index b379e80e18..01c9383a3e 100644 --- a/src/lib/dhcpsrv/cb_ctl_dhcp6.cc +++ b/src/lib/dhcpsrv/cb_ctl_dhcp6.cc @@ -9,6 +9,7 @@ #include <dhcpsrv/cfgmgr.h> #include <dhcpsrv/dhcpsrv_log.h> +using namespace isc::db; using namespace isc::data; using namespace isc::process; @@ -20,11 +21,99 @@ CBControlDHCPv6::databaseConfigApply(const db::BackendSelector& backend_selector const db::ServerSelector& server_selector, const boost::posix_time::ptime& lb_modification_time, const db::AuditEntryCollection& audit_entries) { + bool globals_fetched = false; + + // Let's first delete all the configuration elements for which DELETE audit + // entries are found. Although, this may break chronology of the audit in + // some cases it should not affect the end result of the data fetch. If the + // object was created and then subsequently deleted, we will first try to + // delete this object from the local configuration (which will fail because + // the object does not exist) and then we will try to fetch it from the + // database which will return no result. + if (!audit_entries.empty()) { + + auto cfg = CfgMgr::instance().getCurrentCfg(); + auto external_cfg = CfgMgr::instance().createExternalCfg(); + + // Get audit entries for deleted global parameters. + const auto& index = audit_entries.get<AuditEntryObjectTypeTag>(); + auto range = index.equal_range(boost::make_tuple("dhcp6_global_parameter", + AuditEntry::ModificationType::DELETE)); + if (range.first != range.second) { + // Some globals have been deleted. Since we currently don't track database + // identifiers of the global parameters we have to fetch all global + // parameters for this server. Next, we simply replace existing + // global parameters with the new parameters. This is slightly + // inefficient but only slightly. Note that this is a single + // database query and the number of global parameters is small. + data::StampedValueCollection globals; + globals = getMgr().getPool()->getAllGlobalParameters6(backend_selector, server_selector); + addGlobalsToConfig(external_cfg, globals); + + // Now that we successfully fetched the new global parameters, let's + // remove existing ones and merge them into the current configuration. + cfg->clearConfiguredGlobals(); + CfgMgr::instance().mergeIntoCurrentCfg(external_cfg->getSequence()); + globals_fetched = true; + } + + try { + // Get audit entries for deleted option definitions and delete each + // option definition from the current configuration for which the + // audit entry is found. + range = index.equal_range(boost::make_tuple("dhcp6_option_def", + AuditEntry::ModificationType::DELETE)); + for (auto entry = range.first; entry != range.second; ++entry) { + cfg->getCfgOptionDef()->del((*entry)->getObjectId()); + } + + // Repeat the same for other configuration elements. + + range = index.equal_range(boost::make_tuple("dhcp6_options", + AuditEntry::ModificationType::DELETE)); + for (auto entry = range.first; entry != range.second; ++entry) { + cfg->getCfgOption()->del((*entry)->getObjectId()); + } + + range = index.equal_range(boost::make_tuple("dhcp6_shared_network", + AuditEntry::ModificationType::DELETE)); + for (auto entry = range.first; entry != range.second; ++entry) { + cfg->getCfgSharedNetworks6()->del((*entry)->getObjectId()); + } + + range = index.equal_range(boost::make_tuple("dhcp6_subnet", + AuditEntry::ModificationType::DELETE)); + for (auto entry = range.first; entry != range.second; ++entry) { + // If the deleted subnet belongs to a shared network and the + // shared network is not being removed, we need to detach the + // subnet from the shared network. + auto subnet = cfg->getCfgSubnets6()->getBySubnetId((*entry)->getObjectId()); + if (subnet) { + // Check if the subnet belongs to a shared network. + SharedNetwork6Ptr network; + subnet->getSharedNetwork(network); + if (network) { + // Detach the subnet from the shared network. + network->del(subnet->getID()); + } + // Actually delete the subnet from the configuration. + cfg->getCfgSubnets6()->del((*entry)->getObjectId()); + } + } + + } catch (...) { + // Ignore errors thrown when attempting to delete a non-existing + // configuration entry. There is no guarantee that the deleted + // entry is actually there as we're not processing the audit + // chronologically. + } + } + // 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. - if (fetchConfigElement(audit_entries, "dhcp6_global_parameter")) { + if (!globals_fetched && fetchConfigElement(audit_entries, "dhcp6_global_parameter")) { data::StampedValueCollection globals; globals = getMgr().getPool()->getModifiedGlobalParameters6(backend_selector, server_selector, lb_modification_time); @@ -57,6 +146,12 @@ CBControlDHCPv6::databaseConfigApply(const db::BackendSelector& backend_selector getMgr().getPool()->getModifiedSharedNetworks6(backend_selector, server_selector, lb_modification_time); for (auto network = networks.begin(); network != networks.end(); ++network) { + // In order to take advantage of the dynamic inheritance of global + // parameters to a shared network we need to set a callback function + // for each network to allow for fetching global parameters. + (*network)->setFetchGlobalsFn([] () -> ConstElementPtr { + return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals()); + }); external_cfg->getCfgSharedNetworks6()->add((*network)); } } @@ -67,6 +162,12 @@ CBControlDHCPv6::databaseConfigApply(const db::BackendSelector& backend_selector server_selector, lb_modification_time); for (auto subnet = subnets.begin(); subnet != subnets.end(); ++subnet) { + // In order to take advantage of the dynamic inheritance of global + // parameters to a subnet we need to set a callback function for each + // subnet to allow for fetching global parameters. + (*subnet)->setFetchGlobalsFn([] () -> ConstElementPtr { + return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals()); + }); external_cfg->getCfgSubnets6()->add((*subnet)); } } diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am index b5e3349d4a..313c7bfed1 100644 --- a/src/lib/dhcpsrv/tests/Makefile.am +++ b/src/lib/dhcpsrv/tests/Makefile.am @@ -63,7 +63,8 @@ libdhcpsrv_unittests_SOURCES += alloc_engine_hooks_unittest.cc libdhcpsrv_unittests_SOURCES += alloc_engine4_unittest.cc libdhcpsrv_unittests_SOURCES += alloc_engine6_unittest.cc libdhcpsrv_unittests_SOURCES += callout_handle_store_unittest.cc -libdhcpsrv_unittests_SOURCES += cb_ctl_dhcp_unittest.cc +libdhcpsrv_unittests_SOURCES += cb_ctl_dhcp4_unittest.cc +libdhcpsrv_unittests_SOURCES += cb_ctl_dhcp6_unittest.cc libdhcpsrv_unittests_SOURCES += cfg_db_access_unittest.cc libdhcpsrv_unittests_SOURCES += cfg_duid_unittest.cc libdhcpsrv_unittests_SOURCES += cfg_expiration_unittest.cc diff --git a/src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc b/src/lib/dhcpsrv/tests/cb_ctl_dhcp4_unittest.cc index 7dad1b7f35..7dad1b7f35 100644 --- a/src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc +++ b/src/lib/dhcpsrv/tests/cb_ctl_dhcp4_unittest.cc diff --git a/src/lib/dhcpsrv/tests/cb_ctl_dhcp6_unittest.cc b/src/lib/dhcpsrv/tests/cb_ctl_dhcp6_unittest.cc new file mode 100644 index 0000000000..48f7956b7d --- /dev/null +++ b/src/lib/dhcpsrv/tests/cb_ctl_dhcp6_unittest.cc @@ -0,0 +1,764 @@ +// 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 <asiolink/io_address.h> +#include <cc/stamped_value.h> +#include <dhcp/option_string.h> +#include <dhcpsrv/cb_ctl_dhcp6.h> +#include <dhcpsrv/cfgmgr.h> +#include <dhcpsrv/testutils/generic_backend_unittest.h> +#include <dhcpsrv/testutils/test_config_backend_dhcp6.h> +#include <boost/date_time/posix_time/posix_time.hpp> +#include <gtest/gtest.h> +#include <map> +#include <string> + +using namespace isc::asiolink; +using namespace isc::db; +using namespace isc::data; +using namespace isc::dhcp; +using namespace isc::dhcp::test; +using namespace isc::process; + +namespace { + +/// @brief Base class for testing derivations of the CBControlDHCP. +class CBControlDHCPTest : public GenericBackendTest { +public: + + /// @brief Constructor. + CBControlDHCPTest() + : timestamp_(), object_timestamp_(), audit_entries_() { + CfgMgr::instance().clear(); + CfgMgr::instance().setFamily(AF_INET6); + initTimestamps(); + } + + /// @brief Destructor. + virtual ~CBControlDHCPTest() { + // Unregister the factory to be tidy. + ConfigBackendDHCPv6Mgr::instance().unregisterBackendFactory("memfile"); + CfgMgr::instance().clear(); + } + + /// @brief Creates new CREATE audit entry. + /// + /// The audit entry is added to the @c audit_entries_ collection. + /// + /// @param object_type Object type to be associated with the audit + /// entry. + void addCreateAuditEntry(const std::string& object_type) { + AuditEntryPtr entry(new AuditEntry(object_type, 1234, + AuditEntry::ModificationType::CREATE, + "some log message")); + audit_entries_.insert(entry); + } + + /// @brief Creates new DELETE audit entry. + /// + /// The audit entry is added to the @c audit_entries_ collection. + /// + /// @param object_type Object type to be associated with the audit + /// entry. + /// @param object_id Identifier of the object to be associated with + /// the audit entry. + void addDeleteAuditEntry(const std::string& object_type, + const uint64_t object_id) { + AuditEntryPtr entry(new AuditEntry(object_type, object_id, + AuditEntry::ModificationType::DELETE, + "some log message")); + audit_entries_.insert(entry); + } + + /// @brief Initializes timestamps used in tests. + void initTimestamps() { + // Get the current timestamp and move it 30 seconds backwards. + auto now = boost::posix_time::second_clock::local_time() - + boost::posix_time::seconds(30); + + // Initialize multiple timestamps from the base timestamp. The + // values with indexes [-5, 0] are in the past. The remaining + // four are in the future. + for (int i = -5; i < 5; ++i) { + timestamp_[i] = now + boost::posix_time::minutes(i); + } + } + + /// @brief Returns timestamp associated with a given index. + /// + /// @param timestamp_index Index of the timestamp to be returned. + boost::posix_time::ptime getTimestamp(const int timestamp_index) { + return (timestamp_[timestamp_index]); + } + + /// @brief Returns timestamp to be associated with a given object type. + /// + /// The object types correspond to the names of the SQL tables holding + /// them, e.g. dhcp6_global_parameter, dhcp6_subnet etc. + /// + /// @param object_type Object type. + boost::posix_time::ptime getTimestamp(const std::string& object_type) { + return (object_timestamp_[object_type]); + } + + /// @brief Associates object type with a timestamp. + /// + /// When adding objects to the database, each one is associated with + /// a modification time value. This value is setup by unit tests + /// via this method. + void setTimestamp(const std::string& object_type, const int timestamp_index) { + object_timestamp_[object_type] = timestamp_[timestamp_index]; + } + + /// @brief Sets timestamps for various object types to the same value. + /// + /// @param timestamp_index Index of the timestamp to be set. + virtual void setAllTimestamps(const int timestamp_index) = 0; + + /// @brief Checks if @c databaseConfigApply should fetch updates for specified + /// object types. + /// + /// @param object_type Object type. + bool fetchConfigElement(const std::string& object_type) const { + if (!audit_entries_.empty()) { + const auto& index = audit_entries_.get<AuditEntryObjectTypeTag>(); + auto range = index.equal_range(object_type); + for (auto it = range.first; it != range.second; ++it) { + if (((*it)->getModificationType() != AuditEntry::ModificationType::DELETE)) { + return (true); + } + } + return (false); + } + + return (true); + } + + /// @brief Check if @c databaseConfigApply should delete a given object from the + /// local configuration. + /// + /// @param object_type Object type. + /// @param object_id Object identifier. + bool deleteConfigElement(const std::string& object_type, + const uint64_t object_id) const { + if (!audit_entries_.empty()) { + const auto& index = audit_entries_.get<AuditEntryObjectTypeTag>(); + auto range = index.equal_range(boost::make_tuple(object_type, + AuditEntry::ModificationType::DELETE)); + for (auto it = range.first; it != range.second; ++it) { + if ((*it)->getObjectId() == object_id) { + return (true); + } + } + } + return (false); + } + + /// @brief Holds test timestamps. + std::map<int, boost::posix_time::ptime> timestamp_; + + /// @brief Holds mapping of the objects types to their timestamps. + std::map<std::string, boost::posix_time::ptime> object_timestamp_; + + /// @brief Collection of audit entries used in the unit tests. + AuditEntryCollection audit_entries_; +}; + + +/// @brief Naked @c CBControlDHCPv6 class exposing protected methods. +class TestCBControlDHCPv6 : public CBControlDHCPv6 { +public: + using CBControlDHCPv6::getInitialAuditEntryTime; + using CBControlDHCPv6::databaseConfigApply; +}; + +/// @brief Test fixture class for @c CBControlDHCPv6 unit tests. +class CBControlDHCPv6Test : public CBControlDHCPTest { +public: + + /// @brief Constructor. + CBControlDHCPv6Test() + : CBControlDHCPTest(), ctl_() { + ConfigBackendDHCPv6Mgr::instance().registerBackendFactory("memfile", + [](const DatabaseConnection::ParameterMap& params) + -> ConfigBackendDHCPv6Ptr { + return (TestConfigBackendDHCPv6Ptr(new TestConfigBackendDHCPv6(params))); + }); + ConfigBackendDHCPv6Mgr::instance().addBackend("type=memfile"); + + // By default, set timestamps for all object types to -4. That leaves + // us with the possibility to use index -5 (earlier) to use as lower + // bound modification time so as all objects are fetched. + setAllTimestamps(-4); + } + + /// @brief Sets timestamps of all DHCPv6 specific object types. + /// + /// @param timestamp_index Index of the timestamp to be set. + virtual void setAllTimestamps(const int timestamp_index) { + setTimestamp("dhcp6_global_parameter", timestamp_index); + setTimestamp("dhcp6_option_def", timestamp_index); + setTimestamp("dhcp6_options", timestamp_index); + setTimestamp("dhcp6_shared_network", timestamp_index); + setTimestamp("dhcp6_subnet", timestamp_index); + } + + /// @brief Creates test server configuration and stores it in a test + /// configuration backend. + /// + /// There are pairs of configuration elements stored in the database. + /// For example: two global parameters, two option definitions etc. + /// Having two elements of each type in the database is useful in tests + /// which verify that an element is deleted from the local configuration + /// as a result of being deleted from the configuration backend. In that + /// case the test verifies that one of the elements of the given type + /// is deleted and one is left. + void remoteStoreTestConfiguration() { + auto& mgr = ConfigBackendDHCPv6Mgr::instance(); + + // Insert global parameters into a database. + StampedValuePtr global_parameter = StampedValue::create("foo", "bar"); + global_parameter->setModificationTime(getTimestamp("dhcp6_global_parameter")); + ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter6(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + global_parameter)); + + global_parameter = StampedValue::create("bar", "teta"); + global_parameter->setModificationTime(getTimestamp("dhcp6_global_parameter")); + ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter6(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + global_parameter)); + + // Insert option definitions into the database. + OptionDefinitionPtr def(new OptionDefinition("one", 101, "uint16")); + def->setId(1); + def->setOptionSpaceName("isc"); + def->setModificationTime(getTimestamp("dhcp6_option_def")); + ASSERT_NO_THROW(mgr.getPool()->createUpdateOptionDef6(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + def)); + def.reset(new OptionDefinition("two", 102, "uint16")); + def->setId(2); + def->setOptionSpaceName("isc"); + def->setModificationTime(getTimestamp("dhcp6_option_def")); + ASSERT_NO_THROW(mgr.getPool()->createUpdateOptionDef6(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + def)); + + // Insert global options into the database. + OptionDescriptorPtr opt(new OptionDescriptor(createOption<OptionString> + (Option::V6, D6O_BOOTFILE_URL, + true, false, "some.bootfile"))); + opt->setId(1); + opt->space_name_ = DHCP6_OPTION_SPACE; + opt->setModificationTime(getTimestamp("dhcp6_options")); + mgr.getPool()->createUpdateOption6(BackendSelector::UNSPEC(), ServerSelector::ALL(), + opt); + + opt.reset(new OptionDescriptor(createOption<OptionString> + (Option::V6, D6O_AFTR_NAME, + true, true, "some.example.com"))); + opt->setId(2); + opt->space_name_ = DHCP6_OPTION_SPACE; + opt->setModificationTime(getTimestamp("dhcp6_options")); + mgr.getPool()->createUpdateOption6(BackendSelector::UNSPEC(), ServerSelector::ALL(), + opt); + + // Insert shared networks into the database. + SharedNetwork6Ptr network(new SharedNetwork6("one")); + network->setId(1); + network->setModificationTime(getTimestamp("dhcp6_shared_network")); + mgr.getPool()->createUpdateSharedNetwork6(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + network); + + network.reset(new SharedNetwork6("two")); + network->setId(2); + network->setModificationTime(getTimestamp("dhcp6_shared_network")); + mgr.getPool()->createUpdateSharedNetwork6(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + network); + + // Insert subnets into the database. + Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 64, 1, 2, 3, 4, SubnetID(1))); + subnet->setModificationTime(getTimestamp("dhcp6_subnet")); + subnet->setSharedNetworkName("one"); + mgr.getPool()->createUpdateSubnet6(BackendSelector::UNSPEC(), ServerSelector::ALL(), + subnet); + + subnet.reset(new Subnet6(IOAddress("2001:db8:2::"), 64, 1, 2, 3, 4, SubnetID(2))); + subnet->setModificationTime(getTimestamp("dhcp6_subnet")); + mgr.getPool()->createUpdateSubnet6(BackendSelector::UNSPEC(), ServerSelector::ALL(), + subnet); + } + + /// @brief Deletes specified global parameter from the configuration + /// backend and generates audit entry. + /// + /// Note that the current Kea implementation does not track database + /// identifiers of the global parameters. Therefore, the identifier to + /// be used to create the audit entry for the deleted parameter must + /// be explicitly specified. + /// + /// @param parameter_name Parameter name. + /// @param id Parameter id. + void remoteDeleteGlobalParameter(const std::string& parameter_name, + const uint64_t id) { + auto& mgr = ConfigBackendDHCPv6Mgr::instance(); + mgr.getPool()->deleteGlobalParameter6(BackendSelector::UNSPEC(), ServerSelector::ALL(), + parameter_name); + addDeleteAuditEntry("dhcp6_global_parameter", id); + } + + /// @brief Deletes specified option definition from the configuration + /// backend and generates audit entry. + /// + /// @param code Option code. + /// @param space Option space. + void remoteDeleteOptionDef(const uint16_t code, const std::string& space) { + auto& mgr = ConfigBackendDHCPv6Mgr::instance(); + + auto option_def = mgr.getPool()->getOptionDef6(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + code, space); + + if (option_def) { + mgr.getPool()->deleteOptionDef6(BackendSelector::UNSPEC(), ServerSelector::ALL(), + code, space); + addDeleteAuditEntry("dhcp6_option_def", option_def->getId()); + } + } + + /// @brief Deletes specified global option from the configuration backend + /// and generates audit entry. + /// + /// @param code Option code. + /// @param space Option space. + void remoteDeleteOption(const uint16_t code, const std::string& space) { + auto& mgr = ConfigBackendDHCPv6Mgr::instance(); + + auto option = mgr.getPool()->getOption6(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + code, space); + + if (option) { + mgr.getPool()->deleteOptionDef6(BackendSelector::UNSPEC(), ServerSelector::ALL(), + code, space); + addDeleteAuditEntry("dhcp6_option_def", option->getId()); + } + } + + /// @brief Deletes specified shared network from the configuration backend + /// and generates audit entry. + /// + /// @param name Name of the shared network to be deleted. + void remoteDeleteSharedNetwork(const std::string& name) { + auto& mgr = ConfigBackendDHCPv6Mgr::instance(); + + auto network = mgr.getPool()->getSharedNetwork6(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + name); + + if (network) { + mgr.getPool()->deleteSharedNetwork6(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + name); + addDeleteAuditEntry("dhcp6_shared_network", network->getId()); + } + } + + /// @brief Deletes specified subnet from the configuration backend and + /// generates audit entry. + void remoteDeleteSubnet(const SubnetID& id) { + auto& mgr = ConfigBackendDHCPv6Mgr::instance(); + + mgr.getPool()->deleteSubnet6(BackendSelector::UNSPEC(), ServerSelector::ALL(), + id); + addDeleteAuditEntry("dhcp6_subnet", id); + } + + + /// @brief Tests the @c CBControlDHCPv6::databaseConfigApply method. + /// + /// This test inserts configuration elements of each type into the + /// configuration database. Next, it calls the @c databaseConfigApply, + /// which should merge each object from the database for which the + /// CREATE or UPDATE audit entry is found. The test then verifies + /// if the appropriate entries have been merged. + /// + /// @param lb_modification_time Lower bound modification time to be + /// passed to the @c databaseConfigApply. + void testDatabaseConfigApply(const boost::posix_time::ptime& lb_modification_time) { + remoteStoreTestConfiguration(); + + ASSERT_FALSE(audit_entries_.empty()) + << "Require at least one audit entry. The test is broken!"; + + ctl_.databaseConfigApply(BackendSelector::UNSPEC(), ServerSelector::ALL(), + lb_modification_time, audit_entries_); + + // The updates should have been merged into current configuration. + auto srv_cfg = CfgMgr::instance().getCurrentCfg(); + + // If there is an audit entry for global parameter and the parameter + // modification time is later than last audit entry time it should + // be merged. + if (fetchConfigElement("dhcp6_global_parameter") && + (getTimestamp("dhcp6_global_parameter") > lb_modification_time)) { + checkConfiguredGlobal(srv_cfg, "foo", Element::create("bar")); + + } else { + // Otherwise it shouldn't exist. + EXPECT_FALSE(srv_cfg->getConfiguredGlobals()->get("foo")); + } + + // If there is an audit entry for option definition and the definition + // modification time is later than last audit entry time it should + // be merged. + auto found_def = srv_cfg->getCfgOptionDef()->get("isc", "one"); + if (fetchConfigElement("dhcp6_option_def") && + getTimestamp("dhcp6_option_def") > lb_modification_time) { + ASSERT_TRUE(found_def); + EXPECT_EQ(101, found_def->getCode()); + EXPECT_EQ(OptionDataType::OPT_UINT16_TYPE, found_def->getType()); + + } else { + EXPECT_FALSE(found_def); + } + + // If there is an audit entry for an option and the option + // modification time is later than last audit entry time it should + // be merged. + auto options = srv_cfg->getCfgOption(); + auto found_opt = options->get("dhcp6", D6O_BOOTFILE_URL); + if (fetchConfigElement("dhcp6_options") && + (getTimestamp("dhcp6_options") > lb_modification_time)) { + ASSERT_TRUE(found_opt.option_); + EXPECT_EQ("some.bootfile", found_opt.option_->toString()); + + } else { + EXPECT_FALSE(found_opt.option_); + } + + // If there is an audit entry for a shared network and the network + // modification time is later than last audit entry time it should + // be merged. + auto networks = srv_cfg->getCfgSharedNetworks6(); + auto found_network = networks->getByName("one"); + if (fetchConfigElement("dhcp6_shared_network") && + (getTimestamp("dhcp6_shared_network") > lb_modification_time)) { + ASSERT_TRUE(found_network); + EXPECT_TRUE(found_network->hasFetchGlobalsFn()); + + } else { + EXPECT_FALSE(found_network); + } + + // If there is an audit entry for a subnet and the subnet modification + // time is later than last audit entry time it should be merged. + auto subnets = srv_cfg->getCfgSubnets6(); + auto found_subnet = subnets->getSubnet(1); + if (fetchConfigElement("dhcp6_subnet") && + (getTimestamp("dhcp6_subnet") > lb_modification_time)) { + ASSERT_TRUE(found_subnet); + EXPECT_TRUE(found_subnet->hasFetchGlobalsFn()); + + } else { + EXPECT_FALSE(found_subnet); + } + } + + /// @brief Tests deletion of the configuration elements by the + /// @c CBControlDHCPv6::databaseConfigApply method. + /// + /// This test inserts configuration elements of each type into the + /// configuration database and calls the @c databaseConfigApply + /// to fetch this configuration and merge into the local server + /// configuration. + /// + /// Next, the test calls the specified callback function, i.e. + /// @c db_modifications, which deletes selected configuration + /// elements from the database and generates appropriate audit + /// entries. Finally, it calls the @c databaseConfigApply again + /// to process the audit entries and checks if the appropriate + /// configuration elements are deleted from the local configuration + /// + /// @param lb_modification_time Lower bound modification time to be + /// passed to the @c databaseConfigApply. + /// @param db_modifications Pointer to the callback function which + /// applies test specific modifications into the database. + void testDatabaseConfigApplyDelete(const boost::posix_time::ptime& lb_modification_time, + std::function<void()> db_modifications) { + // Store initial configuration into the database. + remoteStoreTestConfiguration(); + + // Since we pass an empty audit collection the server treats this + // as if the server is starting up and fetches the entire + // configuration from the database. + ctl_.databaseConfigApply(BackendSelector::UNSPEC(), ServerSelector::ALL(), + ctl_.getInitialAuditEntryTime(), + AuditEntryCollection()); + // Commit the configuration so as it is moved from the staging + // to current. + CfgMgr::instance().commit(); + + // Run user defined callback which should delete selected configuration + // elements from the configuration backend. The appropriate DELETE + // audit entries should now be stored in the audit_entries_ collection. + if (db_modifications) { + db_modifications(); + } + + // Process the DELETE audit entries. + ctl_.databaseConfigApply(BackendSelector::UNSPEC(), ServerSelector::ALL(), + lb_modification_time, audit_entries_); + + // All changes should have been applied in the current configuration. + auto srv_cfg = CfgMgr::instance().getCurrentCfg(); + + { + SCOPED_TRACE("global parameters"); + // One of the global parameters should still be there. + EXPECT_TRUE(srv_cfg->getConfiguredGlobals()->get("bar")); + if (deleteConfigElement("dhcp6_global_parameter", 1)) { + EXPECT_FALSE(srv_cfg->getConfiguredGlobals()->get("foo")); + + } else { + EXPECT_TRUE(srv_cfg->getConfiguredGlobals()->get("foo")); + } + } + + { + SCOPED_TRACE("option definitions"); + // One of the option definitions should still be there. + EXPECT_TRUE(srv_cfg->getCfgOptionDef()->get("isc", "two")); + auto found_def = srv_cfg->getCfgOptionDef()->get("isc", "one"); + if (deleteConfigElement("dhcp6_option_def", 1)) { + EXPECT_FALSE(found_def); + + } else { + EXPECT_TRUE(found_def); + } + } + + { + SCOPED_TRACE("global options"); + // One of the options should still be there. + EXPECT_TRUE(srv_cfg->getCfgOption()->get("dhcp6", D6O_AFTR_NAME).option_); + auto found_opt = srv_cfg->getCfgOption()->get("dhcp6", D6O_AFTR_NAME); + if (deleteConfigElement("dhcp6_options", 1)) { + EXPECT_FALSE(found_opt.option_); + + } else { + EXPECT_TRUE(found_opt.option_); + } + } + + { + SCOPED_TRACE("shared networks"); + // One of the shared networks should still be there. + EXPECT_TRUE(srv_cfg->getCfgSharedNetworks6()->getByName("two")); + auto found_network = srv_cfg->getCfgSharedNetworks6()->getByName("one"); + if (deleteConfigElement("dhcp6_shared_network", 1)) { + EXPECT_FALSE(found_network); + + } else { + EXPECT_TRUE(found_network); + } + } + + { + SCOPED_TRACE("subnets"); + // One of the subnets should still be there. + EXPECT_TRUE(srv_cfg->getCfgSubnets6()->getSubnet(2)); + auto found_subnet = srv_cfg->getCfgSubnets6()->getSubnet(1); + if (deleteConfigElement("dhcp6_subnet", 1)) { + EXPECT_FALSE(found_subnet); + // If the subnet has been deleted, make sure that + // it was detached from the shared network it belonged + // to, if the shared network still exists. + auto found_network = srv_cfg->getCfgSharedNetworks6()->getByName("one"); + if (found_network) { + EXPECT_TRUE(found_network->getAllSubnets()->empty()); + } + + } else { + EXPECT_TRUE(found_subnet); + } + } + } + + /// @brief Instance of the @c CBControlDHCPv6 used for testing. + TestCBControlDHCPv6 ctl_; +}; + + +// This test verifies that the configuration updates for all object +// types are merged into the current configuration. +TEST_F(CBControlDHCPv6Test, databaseConfigApplyAll) { + + addCreateAuditEntry("dhcp6_global_parameter"); + addCreateAuditEntry("dhcp6_option_def"); + addCreateAuditEntry("dhcp6_options"); + addCreateAuditEntry("dhcp6_shared_network"); + addCreateAuditEntry("dhcp6_subnet"); + + testDatabaseConfigApply(getTimestamp(-5)); +} + +// This test verifies that multiple configuration elements are +// deleted from the local configuration as a result of being +// deleted from the database. +TEST_F(CBControlDHCPv6Test, databaseConfigApplyDeleteAll) { + testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() { + remoteDeleteGlobalParameter("foo", 1); + remoteDeleteOptionDef(101, "isc"); + remoteDeleteOption(D6O_BOOTFILE_URL, DHCP6_OPTION_SPACE); + remoteDeleteSharedNetwork("one"); + remoteDeleteSubnet(SubnetID(1)); + }); +} + +// This test verifies that an attempt to delete non-existing +// configuration element does not cause an error. +TEST_F(CBControlDHCPv6Test, databaseConfigApplyDeleteNonExisting) { + testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() { + // Add several audit entries instructing to delete the + // non-existing configuration elements. The ids are set + // to 3, but the only existing elements have ids of 1 + // and 2. + addDeleteAuditEntry("dhcp6_global_parameter", 3); + addDeleteAuditEntry("dhcp6_option_def", 3); + addDeleteAuditEntry("dhcp6_options", 3); + addDeleteAuditEntry("dhcp6_shared_network", 3); + addDeleteAuditEntry("dhcp6_subnet", 3); + }); +} + +// This test verifies that only a global parameter is merged into +// the current configuration. +TEST_F(CBControlDHCPv6Test, databaseConfigApplyGlobal) { + addCreateAuditEntry("dhcp6_global_parameter"); + testDatabaseConfigApply(getTimestamp(-5)); +} + +// This test verifies that the global parameter is deleted from +// the local configuration as a result of being deleted from the +// database. +TEST_F(CBControlDHCPv6Test, databaseConfigApplyDeleteGlobal) { + testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() { + remoteDeleteGlobalParameter("foo", 1); + }); +} + +// This test verifies that global parameter is not fetched from the +// database when the modification time is earlier than the last +// fetched audit entry. +TEST_F(CBControlDHCPv6Test, databaseConfigApplyGlobalNotFetched) { + addCreateAuditEntry("dhcp6_global_parameter"); + testDatabaseConfigApply(getTimestamp(-3)); +} + +// This test verifies that only an option definition is merged into +// the current configuration. +TEST_F(CBControlDHCPv6Test, databaseConfigApplyOptionDef) { + addCreateAuditEntry("dhcp6_option_def"); + testDatabaseConfigApply(getTimestamp(-5)); +} + +// This test verifies that the option definition is deleted from +// the local configuration as a result of being deleted from the +// database. +TEST_F(CBControlDHCPv6Test, databaseConfigApplyDeleteOptionDef) { + addDeleteAuditEntry("dhcp6_option_def", 1); + testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() { + remoteDeleteOptionDef(101, "isc"); + }); +} + +// This test verifies that option definition is not fetched from the +// database when the modification time is earlier than the last +// fetched audit entry. +TEST_F(CBControlDHCPv6Test, databaseConfigApplyOptionDefNotFetched) { + addCreateAuditEntry("dhcp6_option_def"); + testDatabaseConfigApply(getTimestamp(-3)); +} + +// This test verifies that only a DHCPv6 option is merged into the +// current configuration. +TEST_F(CBControlDHCPv6Test, databaseConfigApplyOption) { + addCreateAuditEntry("dhcp6_options"); + testDatabaseConfigApply(getTimestamp(-5)); +} + +// This test verifies that the global option is deleted from +// the local configuration as a result of being deleted from the +// database. +TEST_F(CBControlDHCPv6Test, databaseConfigApplyDeleteOption) { + testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() { + remoteDeleteOption(D6O_BOOTFILE_URL, DHCP6_OPTION_SPACE); + }); +} + +// This test verifies that DHCPv6 option is not fetched from the +// database when the modification time is earlier than the last +// fetched audit entry. +TEST_F(CBControlDHCPv6Test, databaseConfigApplyOptionNotFetched) { + addCreateAuditEntry("dhcp6_options"); + testDatabaseConfigApply(getTimestamp(-3)); +} + +// This test verifies that only a shared network is merged into the +// current configuration. +TEST_F(CBControlDHCPv6Test, databaseConfigApplySharedNetwork) { + addCreateAuditEntry("dhcp6_shared_network"); + testDatabaseConfigApply(getTimestamp(-5)); +} + +// This test verifies that the shared network is deleted from +// the local configuration as a result of being deleted from the +// database. +TEST_F(CBControlDHCPv6Test, databaseConfigApplyDeleteSharedNetwork) { + testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() { + remoteDeleteSharedNetwork("one"); + }); +} + +// This test verifies that shared network is not fetched from the +// database when the modification time is earlier than the last +// fetched audit entry. +TEST_F(CBControlDHCPv6Test, databaseConfigApplySharedNetworkNotFetched) { + addCreateAuditEntry("dhcp6_shared_network"); + testDatabaseConfigApply(getTimestamp(-3)); +} + +// This test verifies that only a subnet is merged into the current +// configuration. +TEST_F(CBControlDHCPv6Test, databaseConfigApplySubnet) { + addCreateAuditEntry("dhcp6_shared_network"); + addCreateAuditEntry("dhcp6_subnet"); + testDatabaseConfigApply(getTimestamp(-5)); +} + +// This test verifies that the subnet is deleted from the local +// configuration as a result of being deleted from the database. +TEST_F(CBControlDHCPv6Test, databaseConfigApplyDeleteSubnet) { + testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() { + remoteDeleteSubnet(SubnetID(1)); + }); +} + +// This test verifies that subnet is not fetched from the database +// when the modification time is earlier than the last fetched audit +// entry. +TEST_F(CBControlDHCPv6Test, databaseConfigApplySubnetNotFetched) { + addCreateAuditEntry("dhcp6_subnet"); + testDatabaseConfigApply(getTimestamp(-3)); +} + +} |