]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#566,!304] kea-dhcp6 now removes objects deleted from config backend
authorThomas Markwalder <tmark@isc.org>
Tue, 16 Apr 2019 19:42:08 +0000 (15:42 -0400)
committerThomas Markwalder <tmark@isc.org>
Thu, 18 Apr 2019 14:52:40 +0000 (10:52 -0400)
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

ChangeLog
src/lib/dhcpsrv/cb_ctl_dhcp6.cc
src/lib/dhcpsrv/tests/Makefile.am
src/lib/dhcpsrv/tests/cb_ctl_dhcp4_unittest.cc [moved from src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc with 100% similarity]
src/lib/dhcpsrv/tests/cb_ctl_dhcp6_unittest.cc [new file with mode: 0644]

index 772a3d51525b18cf45a666d2091e861401441feb..fa6c72abf92baeeec4414c01f9b0a5c18c2a6200 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,8 @@
+1560.  [func]          tmark
+       kea-dhcp6 now automatically deletes configuration elements
+       that have been deleted from configuration backends.
+       (Gitlab #566,!304, git TBD)
+
 1559.  [func]          fdupont
        Added DHCPv6 support to the MySQL Config Backend hook.
        (Gitlab #397,!244, git 980091ecd717e41a61f0d7f6808213e450647d8e)
index b379e80e18aab4258fff7eaab74fb33a2f3e2f67..01c9383a3ea23c7977d27ef826073a9f3cd357b5 100644 (file)
@@ -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));
         }
     }
index b5e3349d4aeb001cd1606ef64d52e58bdb138388..313c7bfed1985fa907c03efa4b7ae895c79bbcac 100644 (file)
@@ -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_dhcp6_unittest.cc b/src/lib/dhcpsrv/tests/cb_ctl_dhcp6_unittest.cc
new file mode 100644 (file)
index 0000000..48f7956
--- /dev/null
@@ -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));
+}
+
+}