summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcpsrv
diff options
context:
space:
mode:
authorThomas Markwalder <tmark@isc.org>2019-04-16 21:42:08 +0200
committerThomas Markwalder <tmark@isc.org>2019-04-18 16:52:40 +0200
commitee02fde1f0a4e7d1ba599a20c1b6be86b4f5f9ee (patch)
tree06171cf49535286d6e2f58eeab7cf2bad999c5e0 /src/lib/dhcpsrv
parent[397-cb-implement-mysqlconfigbackenddhcpv6] Added ChangeLog entry (diff)
downloadkea-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.cc103
-rw-r--r--src/lib/dhcpsrv/tests/Makefile.am3
-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.cc764
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));
+}
+
+}