summaryrefslogtreecommitdiffstats
path: root/src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc')
-rw-r--r--src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc1884
1 files changed, 1884 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc b/src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc
new file mode 100644
index 0000000..43a2f79
--- /dev/null
+++ b/src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc
@@ -0,0 +1,1884 @@
+// Copyright (C) 2019-2022 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_dhcp4.h>
+#include <dhcpsrv/cb_ctl_dhcp6.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/client_class_def.h>
+#include <dhcpsrv/host_mgr.h>
+#include <dhcpsrv/testutils/memory_host_data_source.h>
+#include <dhcpsrv/testutils/generic_backend_unittest.h>
+#include <dhcpsrv/testutils/test_config_backend_dhcp4.h>
+#include <dhcpsrv/testutils/test_config_backend_dhcp6.h>
+#include <hooks/server_hooks.h>
+#include <hooks/callout_manager.h>
+#include <hooks/hooks_manager.h>
+#include <testutils/gtest_utils.h>
+#include <boost/date_time/posix_time/posix_time.hpp>
+#include <boost/make_shared.hpp>
+#include <gtest/gtest.h>
+#include <iostream>
+#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;
+using namespace isc::hooks;
+
+namespace {
+
+/// @brief Derivation of the @c MemHostDataSource which always returns
+/// @c false when setting IP reservations unique/non-unique mode.
+class NonUniqueHostDataSource : public MemHostDataSource {
+public:
+
+ /// @brief Virtual destructor.
+ virtual ~NonUniqueHostDataSource() {}
+
+ /// @brief Configure unique/non-unique IP reservations.
+ ///
+ /// @return Always false.
+ virtual bool setIPReservationsUnique(const bool) {
+ return (false);
+ }
+};
+
+/// @brief Pointer to the @c NonUniqueHostDataSource instance.
+typedef boost::shared_ptr<NonUniqueHostDataSource> NonUniqueHostDataSourcePtr;
+
+/// @brief Base class for testing derivations of the CBControlDHCP.
+class CBControlDHCPTest : public GenericBackendTest {
+public:
+
+ /// @brief Constructor.
+ CBControlDHCPTest()
+ : timestamp_(), object_timestamp_(), audit_entries_(),
+ modification_id_(2345) {
+ CfgMgr::instance().clear();
+ initTimestamps();
+ callback_name_ = std::string("");
+ callback_audit_entries_.reset();
+ HostMgr::create();
+ }
+
+ /// @brief Destructor.
+ virtual ~CBControlDHCPTest() {
+ // Unregister the factory to be tidy.
+ ConfigBackendDHCPv4Mgr::instance().unregisterBackendFactory("memfile");
+ CfgMgr::instance().clear();
+ // Unregister hooks.
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("cb4_updated");
+ HooksManager::preCalloutsLibraryHandle().deregisterAllCallouts("cb6_updated");
+ bool status = HooksManager::unloadLibraries();
+ if (!status) {
+ std::cerr << "(fixture dtor) unloadLibraries failed" << std::endl;
+ }
+ HostDataSourceFactory::deregisterFactory("test");
+ }
+
+ /// @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.
+ /// @param object_id Identifier of the object to be associated with
+ /// the audit entry.
+ void addCreateAuditEntry(const std::string& object_type,
+ const uint64_t object_id) {
+ AuditEntryPtr entry(new AuditEntry(object_type, object_id,
+ AuditEntry::ModificationType::CREATE,
+ ++modification_id_,
+ "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,
+ ++modification_id_,
+ "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. dhcp4_global_parameter, dhcp4_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 hasConfigElement(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 Callback that stores received callout name and received value.
+ ///
+ /// @param callout_handle Callout handle.
+ static int
+ cb4_updated_callout(CalloutHandle& callout_handle) {
+ callback_name_ = std::string("cb4_updated");
+ callout_handle.getArgument("audit_entries", callback_audit_entries_);
+ return (0);
+ }
+
+ /// @brief Callback that stores received callout name and received value.
+ ///
+ /// @param callout_handle Callout handle.
+ static int
+ cb6_updated_callout(CalloutHandle& callout_handle) {
+ callback_name_ = std::string("cb6_updated");
+ callout_handle.getArgument("audit_entries", callback_audit_entries_);
+ return (0);
+ }
+
+ /// @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 Modification id counter.
+ uint64_t modification_id_;
+
+ /// @brief Callback name.
+ static std::string callback_name_;
+
+ /// @brief Callback value.
+ static AuditEntryCollectionPtr callback_audit_entries_;
+};
+
+std::string CBControlDHCPTest::callback_name_;
+AuditEntryCollectionPtr CBControlDHCPTest::callback_audit_entries_;
+
+// ************************ V4 tests *********************
+
+/// @brief Naked @c CBControlDHCPv4 class exposing protected methods.
+class TestCBControlDHCPv4 : public CBControlDHCPv4 {
+public:
+ /// @brief Constructor.
+ TestCBControlDHCPv4() {
+ CfgMgr::instance().setFamily(AF_INET);
+ }
+
+ using CBControlDHCPv4::getInitialAuditRevisionTime;
+ using CBControlDHCPv4::databaseConfigApply;
+};
+
+/// @brief Test fixture class for @c CBControlDHCPv4 unit tests.
+class CBControlDHCPv4Test : public CBControlDHCPTest {
+public:
+
+ /// @brief Constructor.
+ CBControlDHCPv4Test()
+ : CBControlDHCPTest(), ctl_() {
+ ConfigBackendDHCPv4Mgr::instance().registerBackendFactory("memfile",
+ [](const DatabaseConnection::ParameterMap& params)
+ -> ConfigBackendDHCPv4Ptr {
+ return (TestConfigBackendDHCPv4Ptr(new TestConfigBackendDHCPv4(params)));
+ });
+ ConfigBackendDHCPv4Mgr::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 DHCPv4 specific object types.
+ ///
+ /// @param timestamp_index Index of the timestamp to be set.
+ virtual void setAllTimestamps(const int timestamp_index) {
+ setTimestamp("dhcp4_global_parameter", timestamp_index);
+ setTimestamp("dhcp4_option_def", timestamp_index);
+ setTimestamp("dhcp4_options", timestamp_index);
+ setTimestamp("dhcp4_shared_network", timestamp_index);
+ setTimestamp("dhcp4_subnet", timestamp_index);
+ setTimestamp("dhcp4_client_class", 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 = ConfigBackendDHCPv4Mgr::instance();
+
+ // Insert global parameters into a database.
+ StampedValuePtr global_parameter = StampedValue::create("comment", "bar");
+ global_parameter->setModificationTime(getTimestamp("dhcp4_global_parameter"));
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ global_parameter));
+
+ global_parameter = StampedValue::create("next-server", "teta");
+ global_parameter->setModificationTime(getTimestamp("dhcp4_global_parameter"));
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ global_parameter));
+
+ // Insert option definitions into the database.
+ OptionDefinitionPtr def(new OptionDefinition("one", 101, "isc", "uint16"));
+ def->setId(1);
+ def->setModificationTime(getTimestamp("dhcp4_option_def"));
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateOptionDef4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ def));
+ def.reset(new OptionDefinition("two", 102, "isc", "uint16"));
+ def->setId(2);
+ def->setModificationTime(getTimestamp("dhcp4_option_def"));
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateOptionDef4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ def));
+
+ // Insert global options into the database.
+ OptionDescriptorPtr opt(new OptionDescriptor(createOption<OptionString>
+ (Option::V4, DHO_HOST_NAME,
+ true, false, "new.example.com")));
+ opt->setId(1);
+ opt->space_name_ = DHCP4_OPTION_SPACE;
+ opt->setModificationTime(getTimestamp("dhcp4_options"));
+ mgr.getPool()->createUpdateOption4(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ opt);
+
+ opt.reset(new OptionDescriptor(createOption<OptionString>
+ (Option::V4, DHO_TFTP_SERVER_NAME,
+ true, false, "tftp-my")));
+ opt->setId(2);
+ opt->space_name_ = DHCP4_OPTION_SPACE;
+ opt->setModificationTime(getTimestamp("dhcp4_options"));
+ mgr.getPool()->createUpdateOption4(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ opt);
+
+ // Insert shared networks into the database.
+ SharedNetwork4Ptr network(new SharedNetwork4("one"));
+ network->setId(1);
+ network->setModificationTime(getTimestamp("dhcp4_shared_network"));
+ mgr.getPool()->createUpdateSharedNetwork4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ network);
+
+ network.reset(new SharedNetwork4("two"));
+ network->setId(2);
+ network->setModificationTime(getTimestamp("dhcp4_shared_network"));
+ mgr.getPool()->createUpdateSharedNetwork4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ network);
+
+ // Insert subnets into the database.
+ Subnet4Ptr subnet(new Subnet4(IOAddress("192.0.3.0"), 26, 1, 2, 3, SubnetID(1)));
+ subnet->setModificationTime(getTimestamp("dhcp4_subnet"));
+ subnet->setSharedNetworkName("one");
+ mgr.getPool()->createUpdateSubnet4(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ subnet);
+
+ subnet.reset(new Subnet4(IOAddress("192.0.4.0"), 26, 1, 2, 3, SubnetID(2)));
+ subnet->setModificationTime(getTimestamp("dhcp4_subnet"));
+ mgr.getPool()->createUpdateSubnet4(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ subnet);
+
+ // Insert client classes into the database.
+ auto expression = boost::make_shared<Expression>();
+ ClientClassDefPtr client_class = boost::make_shared<ClientClassDef>("first-class", expression);
+ client_class->setTest("substring(option[1].hex, 0, 8) == 'my-value'");
+ client_class->setId(1);
+ client_class->setModificationTime(getTimestamp("dhcp4_client_class"));
+
+ // Add a standard option to the class.
+ OptionPtr option = Option::create(Option::V4, DHO_BOOT_FILE_NAME);
+ OptionDescriptorPtr desc = OptionDescriptor::create(option, true, "bogus-file.txt");
+ desc->space_name_ = DHCP4_OPTION_SPACE;
+ desc->setModificationTime(getTimestamp("dhcp4_client_class"));
+ client_class->getCfgOption()->add(*desc, desc->space_name_);
+
+ // Add a custom option definition to the class.
+ CfgOptionDefPtr cc_cfg_option_def(new CfgOptionDef());
+ def.reset(new OptionDefinition("v4str", 201, "isc", "string"));
+ def->setId(201);
+ def->setModificationTime(getTimestamp("dhcp4_client_class"));
+ cc_cfg_option_def->add(def);
+ client_class->setCfgOptionDef(cc_cfg_option_def);
+
+ // Add a custom option to the class.
+ option = Option::create(Option::V4, 201);
+ desc = OptionDescriptor::create(option, true, "custom-stuff");
+ desc->space_name_ = "isc";
+ desc->setModificationTime(getTimestamp("dhcp4_client_class"));
+ client_class->getCfgOption()->add(*desc, desc->space_name_);
+
+ mgr.getPool()->createUpdateClientClass4(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ client_class, "");
+
+ client_class = boost::make_shared<ClientClassDef>("second-class", expression);
+ client_class->setId(2);
+ client_class->setModificationTime(getTimestamp("dhcp4_client_class"));
+ mgr.getPool()->createUpdateClientClass4(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ client_class, "");
+ }
+
+ /// @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 = ConfigBackendDHCPv4Mgr::instance();
+ mgr.getPool()->deleteGlobalParameter4(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ parameter_name);
+ addDeleteAuditEntry("dhcp4_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 = ConfigBackendDHCPv4Mgr::instance();
+
+ auto option_def = mgr.getPool()->getOptionDef4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ code, space);
+
+ if (option_def) {
+ mgr.getPool()->deleteOptionDef4(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ code, space);
+ addDeleteAuditEntry("dhcp4_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 = ConfigBackendDHCPv4Mgr::instance();
+
+ auto option = mgr.getPool()->getOption4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ code, space);
+
+ if (option) {
+ mgr.getPool()->deleteOptionDef4(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ code, space);
+ addDeleteAuditEntry("dhcp4_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 = ConfigBackendDHCPv4Mgr::instance();
+
+ auto network = mgr.getPool()->getSharedNetwork4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ name);
+
+ if (network) {
+ mgr.getPool()->deleteSharedNetwork4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ name);
+ addDeleteAuditEntry("dhcp4_shared_network", network->getId());
+ }
+ }
+
+ /// @brief Deletes specified subnet from the configuration backend and
+ /// generates audit entry.
+ void remoteDeleteSubnet(const SubnetID& id) {
+ auto& mgr = ConfigBackendDHCPv4Mgr::instance();
+
+ mgr.getPool()->deleteSubnet4(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ id);
+ addDeleteAuditEntry("dhcp4_subnet", id);
+ }
+
+ /// @brief Deletes specified client class from the configuration backend
+ /// and generates audit entry.
+ ///
+ /// @param name Name of the client class to be deleted.
+ void remoteDeleteClientClass(const std::string& name) {
+ auto& mgr = ConfigBackendDHCPv4Mgr::instance();
+
+ auto client_class = mgr.getPool()->getClientClass4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ name);
+
+ if (client_class) {
+ mgr.getPool()->deleteClientClass4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ name);
+ addDeleteAuditEntry("dhcp4_client_class", client_class->getId());
+ }
+ }
+
+ /// @brief Tests the @c CBControlDHCPv4::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!";
+
+ ASSERT_NO_THROW_LOG(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 revision time it should
+ // be merged.
+ if (hasConfigElement("dhcp4_global_parameter") &&
+ (getTimestamp("dhcp4_global_parameter") > lb_modification_time)) {
+ checkConfiguredGlobal(srv_cfg, "comment", Element::create("bar"));
+
+ } else {
+ // Otherwise it shouldn't exist.
+ EXPECT_FALSE(srv_cfg->getConfiguredGlobals()->get("comment"));
+ }
+
+ // If there is an audit entry for option definition and the definition
+ // modification time is later than last audit revision time it should
+ // be merged.
+ auto found_def = srv_cfg->getCfgOptionDef()->get("isc", "one");
+ if (hasConfigElement("dhcp4_option_def") &&
+ getTimestamp("dhcp4_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 revision time it should
+ // be merged.
+ auto options = srv_cfg->getCfgOption();
+ auto found_opt = options->get(DHCP4_OPTION_SPACE, DHO_HOST_NAME);
+ if (hasConfigElement("dhcp4_options") &&
+ (getTimestamp("dhcp4_options") > lb_modification_time)) {
+ ASSERT_TRUE(found_opt.option_);
+ EXPECT_EQ("new.example.com", 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 revision time it should
+ // be merged.
+ auto networks = srv_cfg->getCfgSharedNetworks4();
+ auto found_network = networks->getByName("one");
+ if (hasConfigElement("dhcp4_shared_network") &&
+ (getTimestamp("dhcp4_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 revision time it should be merged.
+ auto subnets = srv_cfg->getCfgSubnets4();
+ auto found_subnet = subnets->getBySubnetId(1);
+ if (hasConfigElement("dhcp4_subnet") &&
+ (getTimestamp("dhcp4_subnet") > lb_modification_time)) {
+ ASSERT_TRUE(found_subnet);
+ EXPECT_TRUE(found_subnet->hasFetchGlobalsFn());
+
+ } else {
+ EXPECT_FALSE(found_subnet);
+ }
+
+ auto client_classes = srv_cfg->getClientClassDictionary();
+ auto found_class = client_classes->findClass("first-class");
+ if (hasConfigElement("dhcp4_client_class") &&
+ (getTimestamp("dhcp4_client_class") > lb_modification_time)) {
+ ASSERT_TRUE(found_class);
+ ASSERT_TRUE(found_class->getMatchExpr());
+ EXPECT_GT(found_class->getMatchExpr()->size(), 0);
+ EXPECT_EQ("first-class", found_class->getName());
+
+ // Check for the standard class option, make sure it has been "created".
+ auto cfg_option_desc = found_class->getCfgOption();
+ ASSERT_TRUE(cfg_option_desc);
+ auto option_desc = cfg_option_desc->get(DHCP4_OPTION_SPACE, DHO_BOOT_FILE_NAME);
+ auto option = option_desc.option_;
+ ASSERT_TRUE(option);
+ EXPECT_EQ(OptionBuffer(option_desc.formatted_value_.begin(),
+ option_desc.formatted_value_.end()),
+ option->getData());
+
+ // Check for the custom class option, make sure it has been "created".
+ option_desc = cfg_option_desc->get("isc", 201);
+ option = option_desc.option_;
+ ASSERT_TRUE(option);
+ EXPECT_EQ(OptionBuffer(option_desc.formatted_value_.begin(),
+ option_desc.formatted_value_.end()),
+ option->getData());
+ } else {
+ EXPECT_FALSE(found_class);
+ }
+ }
+
+ /// @brief Tests deletion of the configuration elements by the
+ /// @c CBControlDHCPv4::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_.getInitialAuditRevisionTime(),
+ 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("next-server"));
+ if (deleteConfigElement("dhcp4_global_parameter", 1)) {
+ EXPECT_FALSE(srv_cfg->getConfiguredGlobals()->get("comment"));
+
+ } else {
+ EXPECT_TRUE(srv_cfg->getConfiguredGlobals()->get("next-server"));
+ }
+ }
+
+ {
+ 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("dhcp4_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(DHCP4_OPTION_SPACE,
+ DHO_TFTP_SERVER_NAME).option_);
+ auto found_opt = srv_cfg->getCfgOption()->get(DHCP4_OPTION_SPACE,
+ DHO_HOST_NAME);
+ if (deleteConfigElement("dhcp4_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->getCfgSharedNetworks4()->getByName("two"));
+ auto found_network = srv_cfg->getCfgSharedNetworks4()->getByName("one");
+ if (deleteConfigElement("dhcp4_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->getCfgSubnets4()->getBySubnetId(2));
+ auto found_subnet = srv_cfg->getCfgSubnets4()->getBySubnetId(1);
+ if (deleteConfigElement("dhcp4_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->getCfgSharedNetworks4()->getByName("one");
+ if (found_network) {
+ EXPECT_TRUE(found_network->getAllSubnets()->empty());
+ }
+
+ } else {
+ EXPECT_TRUE(found_subnet);
+ }
+ }
+
+ {
+ SCOPED_TRACE("client classes");
+ // One of the subnets should still be there.
+ EXPECT_TRUE(srv_cfg->getClientClassDictionary()->findClass("second-class"));
+ auto found_client_class = srv_cfg->getClientClassDictionary()->findClass("first-class");
+ if (deleteConfigElement("dhcp4_client_class", 1)) {
+ EXPECT_FALSE(found_client_class);
+
+ } else {
+ EXPECT_TRUE(found_client_class);
+ }
+ }
+ }
+
+ /// @brief Instance of the @c CBControlDHCPv4 used for testing.
+ TestCBControlDHCPv4 ctl_;
+};
+
+
+// This test verifies that the configuration updates for all object
+// types are merged into the current configuration.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyAll) {
+
+ addCreateAuditEntry("dhcp4_global_parameter", 1);
+ addCreateAuditEntry("dhcp4_global_parameter", 2);
+ addCreateAuditEntry("dhcp4_option_def", 1);
+ addCreateAuditEntry("dhcp4_option_def", 2);
+ addCreateAuditEntry("dhcp4_options", 1);
+ addCreateAuditEntry("dhcp4_options", 2);
+ addCreateAuditEntry("dhcp4_shared_network", 1);
+ addCreateAuditEntry("dhcp4_shared_network", 2);
+ addCreateAuditEntry("dhcp4_subnet", 1);
+ addCreateAuditEntry("dhcp4_subnet", 2);
+ addCreateAuditEntry("dhcp4_client_class", 1);
+ addCreateAuditEntry("dhcp4_client_class", 2);
+
+ 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(CBControlDHCPv4Test, databaseConfigApplyDeleteAll) {
+ testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() {
+ remoteDeleteGlobalParameter("comment", 1);
+ remoteDeleteOptionDef(101, "isc");
+ remoteDeleteOption(DHO_HOST_NAME, DHCP4_OPTION_SPACE);
+ remoteDeleteSharedNetwork("one");
+ remoteDeleteSubnet(SubnetID(1));
+ remoteDeleteClientClass("first-class");
+ });
+}
+
+// This test verifies that an attempt to delete non-existing
+// configuration element does not cause an error.
+TEST_F(CBControlDHCPv4Test, 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("dhcp4_global_parameter", 3);
+ addDeleteAuditEntry("dhcp4_option_def", 3);
+ addDeleteAuditEntry("dhcp4_options", 3);
+ addDeleteAuditEntry("dhcp4_shared_network", 3);
+ addDeleteAuditEntry("dhcp4_subnet", 3);
+ addDeleteAuditEntry("dhcp4_client_class", 3);
+ });
+}
+
+// This test verifies that only a global parameter is merged into
+// the current configuration.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyGlobal) {
+ addCreateAuditEntry("dhcp4_global_parameter", 1);
+ addCreateAuditEntry("dhcp4_global_parameter", 2);
+ 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(CBControlDHCPv4Test, databaseConfigApplyDeleteGlobal) {
+ testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() {
+ remoteDeleteGlobalParameter("comment", 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(CBControlDHCPv4Test, databaseConfigApplyGlobalNotFetched) {
+ addCreateAuditEntry("dhcp4_global_parameter", 1);
+ addCreateAuditEntry("dhcp4_global_parameter", 2);
+ testDatabaseConfigApply(getTimestamp(-3));
+}
+
+// This test verifies that only an option definition is merged into
+// the current configuration.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyOptionDef) {
+ addCreateAuditEntry("dhcp4_option_def", 1);
+ addCreateAuditEntry("dhcp4_option_def", 2);
+ 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(CBControlDHCPv4Test, databaseConfigApplyDeleteOptionDef) {
+ addDeleteAuditEntry("dhcp4_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(CBControlDHCPv4Test, databaseConfigApplyOptionDefNotFetched) {
+ addCreateAuditEntry("dhcp4_option_def", 1);
+ addCreateAuditEntry("dhcp4_option_def", 2);
+ testDatabaseConfigApply(getTimestamp(-3));
+}
+
+// This test verifies that only a DHCPv4 option is merged into the
+// current configuration.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyOption) {
+ addCreateAuditEntry("dhcp4_options", 1);
+ addCreateAuditEntry("dhcp4_options", 2);
+ 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(CBControlDHCPv4Test, databaseConfigApplyDeleteOption) {
+ testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() {
+ remoteDeleteOption(DHO_HOST_NAME, DHCP4_OPTION_SPACE);
+ });
+}
+
+// This test verifies that DHCPv4 option is not fetched from the
+// database when the modification time is earlier than the last
+// fetched audit entry.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyOptionNotFetched) {
+ addCreateAuditEntry("dhcp4_options", 1);
+ addCreateAuditEntry("dhcp4_options", 2);
+ testDatabaseConfigApply(getTimestamp(-3));
+}
+
+// This test verifies that only a shared network is merged into the
+// current configuration.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplySharedNetwork) {
+ addCreateAuditEntry("dhcp4_shared_network", 1);
+ addCreateAuditEntry("dhcp4_shared_network", 2);
+ 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(CBControlDHCPv4Test, 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(CBControlDHCPv4Test, databaseConfigApplySharedNetworkNotFetched) {
+ addCreateAuditEntry("dhcp4_shared_network", 1);
+ addCreateAuditEntry("dhcp4_shared_network", 2);
+ testDatabaseConfigApply(getTimestamp(-3));
+}
+
+// This test verifies that only a subnet is merged into the current
+// configuration.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplySubnet) {
+ addCreateAuditEntry("dhcp4_shared_network", 1);
+ addCreateAuditEntry("dhcp4_shared_network", 2);
+ addCreateAuditEntry("dhcp4_subnet", 1);
+ addCreateAuditEntry("dhcp4_subnet", 2);
+ 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(CBControlDHCPv4Test, 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(CBControlDHCPv4Test, databaseConfigApplySubnetNotFetched) {
+ addCreateAuditEntry("dhcp4_subnet", 1);
+ addCreateAuditEntry("dhcp4_subnet", 2);
+ testDatabaseConfigApply(getTimestamp(-3));
+}
+
+// This test verifies that only client classes are merged into the current
+// configuration.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyClientClasses) {
+ addCreateAuditEntry("dhcp4_client_class", 1);
+ addCreateAuditEntry("dhcp4_client_class", 2);
+ testDatabaseConfigApply(getTimestamp(-5));
+}
+
+// This test verifies that a client class is deleted from the local
+// configuration as a result of being deleted from the database.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyDeleteClientClass) {
+ testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() {
+ remoteDeleteClientClass("first-class");
+ });
+}
+
+// This test verifies that the configuration update calls the hook.
+TEST_F(CBControlDHCPv4Test, databaseConfigApplyHook) {
+
+ // Initialize Hooks Manager.
+ HooksManager::loadLibraries(HookLibsCollection());
+
+ // Install cb4_updated.
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "cb4_updated", cb4_updated_callout));
+
+ // Create audit entries.
+ addCreateAuditEntry("dhcp4_global_parameter", 1);
+ addCreateAuditEntry("dhcp4_global_parameter", 2);
+ addCreateAuditEntry("dhcp4_option_def", 1);
+ addCreateAuditEntry("dhcp4_option_def", 2);
+ addCreateAuditEntry("dhcp4_options", 1);
+ addCreateAuditEntry("dhcp4_options", 2);
+ addCreateAuditEntry("dhcp4_shared_network", 1);
+ addCreateAuditEntry("dhcp4_shared_network", 2);
+ addCreateAuditEntry("dhcp4_subnet", 1);
+ addCreateAuditEntry("dhcp4_subnet", 2);
+
+ // Run the test.
+ testDatabaseConfigApply(getTimestamp(-5));
+
+ // Checks the callout.
+ EXPECT_EQ("cb4_updated", callback_name_);
+ ASSERT_TRUE(callback_audit_entries_);
+ EXPECT_TRUE(audit_entries_ == *callback_audit_entries_);
+}
+
+// This test verifies that it is possible to set ip-reservations-unique
+// parameter via configuration backend and that it is successful when
+// host database backend accepts the new setting.
+TEST_F(CBControlDHCPv4Test, ipReservationsNonUniqueAccepted) {
+ // Create host data source which accepts setting non-unique IP
+ // reservations.
+ MemHostDataSourcePtr hds(new MemHostDataSource());
+ auto testFactory = [hds](const DatabaseConnection::ParameterMap&) {
+ return (hds);
+ };
+ HostDataSourceFactory::registerFactory("test", testFactory);
+ HostMgr::addBackend("type=test");
+
+ // Insert ip-reservations-unique value set to false into the database.
+ auto& mgr = ConfigBackendDHCPv4Mgr::instance();
+ StampedValuePtr global_parameter = StampedValue::create("ip-reservations-unique",
+ Element::create(false));
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ global_parameter));
+ // Adding audit entry simulates the case when the server is already configured
+ // and we're adding incremental changes. These changes should be applied to
+ // the current configuration.
+ addCreateAuditEntry("dhcp4_global_parameter", 1);
+
+ // Apply the configuration.
+ ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ getTimestamp(-5),
+ audit_entries_));
+ // The new setting should be visible in both CfgDbAccess and HostMgr.
+ EXPECT_FALSE(CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->getIPReservationsUnique());
+ EXPECT_FALSE(HostMgr::instance().getIPReservationsUnique());
+}
+
+// This test verifies that the new setting of ip-reservations-unique is not
+// accepted when one of the host database backends does not support it.
+TEST_F(CBControlDHCPv4Test, ipReservationsNonUniqueRefused) {
+ // Create host data source which does not accept setting IP reservations
+ // non-unique setting.
+ NonUniqueHostDataSourcePtr hds(new NonUniqueHostDataSource());
+ auto testFactory = [hds](const DatabaseConnection::ParameterMap&) {
+ return (hds);
+ };
+ HostDataSourceFactory::registerFactory("test", testFactory);
+ HostMgr::addBackend("type=test");
+
+ // Insert ip-reservations-unique value set to false into the database.
+ auto& mgr = ConfigBackendDHCPv4Mgr::instance();
+ StampedValuePtr global_parameter = StampedValue::create("ip-reservations-unique",
+ Element::create(false));
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter4(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ global_parameter));
+ // Adding audit entry simulates the case when the server is already configured
+ // and we're adding incremental changes. These changes should be applied to
+ // the current configuration.
+ addCreateAuditEntry("dhcp4_global_parameter", 1);
+
+ // Apply the configuration.
+ ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ getTimestamp(-5),
+ audit_entries_));
+ // The default setting should be applied, because the backend refused to
+ // set it to false.
+ EXPECT_TRUE(CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->getIPReservationsUnique());
+ EXPECT_TRUE(HostMgr::instance().getIPReservationsUnique());
+}
+
+// ************************ V6 tests *********************
+
+/// @brief Naked @c CBControlDHCPv6 class exposing protected methods.
+class TestCBControlDHCPv6 : public CBControlDHCPv6 {
+public:
+ /// @brief Constructor.
+ TestCBControlDHCPv6() {
+ CfgMgr::instance().setFamily(AF_INET6);
+ }
+
+ using CBControlDHCPv6::getInitialAuditRevisionTime;
+ 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);
+ setTimestamp("dhcp6_client_class", 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("comment", "bar");
+ global_parameter->setModificationTime(getTimestamp("dhcp6_global_parameter"));
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ global_parameter));
+
+ global_parameter = StampedValue::create("data-directory", "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, "isc", "uint16"));
+ def->setId(1);
+ def->setModificationTime(getTimestamp("dhcp6_option_def"));
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateOptionDef6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ def));
+ def.reset(new OptionDefinition("two", 102, "isc", "uint16"));
+ def->setId(2);
+ 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);
+
+ // Insert client classes into the database.
+ auto expression = boost::make_shared<Expression>();
+ ClientClassDefPtr client_class = boost::make_shared<ClientClassDef>("first-class", expression);
+ client_class->setTest("substring(option[1].hex, 0, 8) == 'my-value'");
+ client_class->setId(1);
+ client_class->setModificationTime(getTimestamp("dhcp6_client_class"));
+
+ // Add an option to the class.
+ OptionPtr option = Option::create(Option::V6, D6O_BOOTFILE_URL);
+ OptionDescriptorPtr desc = OptionDescriptor::create(option, true, "client.boot.url");
+ desc->space_name_ = DHCP6_OPTION_SPACE;
+ desc->setModificationTime(getTimestamp("dhcp6_client_class"));
+ client_class->getCfgOption()->add(*desc, desc->space_name_);
+
+ mgr.getPool()->createUpdateClientClass6(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ client_class, "");
+
+ client_class = boost::make_shared<ClientClassDef>("second-class", expression);
+ client_class->setId(2);
+ client_class->setModificationTime(getTimestamp("dhcp6_client_class"));
+ mgr.getPool()->createUpdateClientClass6(BackendSelector::UNSPEC(), ServerSelector::ALL(),
+ client_class, "");
+ }
+
+ /// @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 Deletes specified client class from the configuration backend
+ /// and generates audit entry.
+ ///
+ /// @param name Name of the client class to be deleted.
+ void remoteDeleteClientClass(const std::string& name) {
+ auto& mgr = ConfigBackendDHCPv6Mgr::instance();
+
+ auto client_class = mgr.getPool()->getClientClass6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ name);
+
+ if (client_class) {
+ mgr.getPool()->deleteClientClass6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ name);
+ addDeleteAuditEntry("dhcp6_client_class", client_class->getId());
+ }
+ }
+
+ /// @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 revision time it should
+ // be merged.
+ if (hasConfigElement("dhcp6_global_parameter") &&
+ (getTimestamp("dhcp6_global_parameter") > lb_modification_time)) {
+ checkConfiguredGlobal(srv_cfg, "comment", Element::create("bar"));
+
+ } else {
+ // Otherwise it shouldn't exist.
+ EXPECT_FALSE(srv_cfg->getConfiguredGlobals()->get("comment"));
+ }
+
+ // If there is an audit entry for option definition and the definition
+ // modification time is later than last audit revision time it should
+ // be merged.
+ auto found_def = srv_cfg->getCfgOptionDef()->get("isc", "one");
+ if (hasConfigElement("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 revision time it should
+ // be merged.
+ auto options = srv_cfg->getCfgOption();
+ auto found_opt = options->get(DHCP6_OPTION_SPACE, D6O_BOOTFILE_URL);
+ if (hasConfigElement("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 revision time it should
+ // be merged.
+ auto networks = srv_cfg->getCfgSharedNetworks6();
+ auto found_network = networks->getByName("one");
+ if (hasConfigElement("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 revision time it should be merged.
+ auto subnets = srv_cfg->getCfgSubnets6();
+ auto found_subnet = subnets->getBySubnetId(1);
+ if (hasConfigElement("dhcp6_subnet") &&
+ (getTimestamp("dhcp6_subnet") > lb_modification_time)) {
+ ASSERT_TRUE(found_subnet);
+ EXPECT_TRUE(found_subnet->hasFetchGlobalsFn());
+
+ } else {
+ EXPECT_FALSE(found_subnet);
+ }
+
+ auto client_classes = srv_cfg->getClientClassDictionary();
+ auto found_class = client_classes->findClass("first-class");
+ if (hasConfigElement("dhcp6_client_class") &&
+ (getTimestamp("dhcp6_client_class") > lb_modification_time)) {
+ ASSERT_TRUE(found_class);
+ ASSERT_TRUE(found_class->getMatchExpr());
+ EXPECT_GT(found_class->getMatchExpr()->size(), 0);
+ EXPECT_EQ("first-class", found_class->getName());
+
+ // Check for class option, make sure it has been "created".
+ auto cfg_option_desc = found_class->getCfgOption();
+ ASSERT_TRUE(cfg_option_desc);
+ auto option_desc = cfg_option_desc->get(DHCP6_OPTION_SPACE, D6O_BOOTFILE_URL);
+ auto option = option_desc.option_;
+ ASSERT_TRUE(option);
+ EXPECT_EQ(OptionBuffer(option_desc.formatted_value_.begin(),
+ option_desc.formatted_value_.end()),
+ option->getData());
+ } else {
+ EXPECT_FALSE(found_class);
+ }
+ }
+
+ /// @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_.getInitialAuditRevisionTime(),
+ 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("data-directory"));
+ if (deleteConfigElement("dhcp6_global_parameter", 1)) {
+ EXPECT_FALSE(srv_cfg->getConfiguredGlobals()->get("comment"));
+
+ } else {
+ EXPECT_TRUE(srv_cfg->getConfiguredGlobals()->get("comment"));
+ }
+ }
+
+ {
+ 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_OPTION_SPACE,
+ D6O_AFTR_NAME).option_);
+ auto found_opt = srv_cfg->getCfgOption()->get(DHCP6_OPTION_SPACE,
+ 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()->getBySubnetId(2));
+ auto found_subnet = srv_cfg->getCfgSubnets6()->getBySubnetId(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", 1);
+ addCreateAuditEntry("dhcp6_global_parameter", 2);
+ addCreateAuditEntry("dhcp6_option_def", 1);
+ addCreateAuditEntry("dhcp6_option_def", 2);
+ addCreateAuditEntry("dhcp6_options", 1);
+ addCreateAuditEntry("dhcp6_options", 2);
+ addCreateAuditEntry("dhcp6_shared_network", 1);
+ addCreateAuditEntry("dhcp6_shared_network", 2);
+ addCreateAuditEntry("dhcp6_subnet", 1);
+ addCreateAuditEntry("dhcp6_subnet", 2);
+ addCreateAuditEntry("dhcp6_client_class", 1);
+ addCreateAuditEntry("dhcp6_client_class", 2);
+
+ 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("comment", 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", 1);
+ addCreateAuditEntry("dhcp6_global_parameter", 2);
+ 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("comment", 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", 1);
+ addCreateAuditEntry("dhcp6_global_parameter", 2);
+ 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", 1);
+ addCreateAuditEntry("dhcp6_option_def", 2);
+ 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", 1);
+ addCreateAuditEntry("dhcp6_option_def", 2);
+ testDatabaseConfigApply(getTimestamp(-3));
+}
+
+// This test verifies that only a DHCPv6 option is merged into the
+// current configuration.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplyOption) {
+ addCreateAuditEntry("dhcp6_options", 1);
+ addCreateAuditEntry("dhcp6_options", 2);
+ 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", 1);
+ addCreateAuditEntry("dhcp6_options", 2);
+ 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", 1);
+ addCreateAuditEntry("dhcp6_shared_network", 2);
+ 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", 1);
+ addCreateAuditEntry("dhcp6_shared_network", 2);
+ testDatabaseConfigApply(getTimestamp(-3));
+}
+
+// This test verifies that only a subnet is merged into the current
+// configuration.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplySubnet) {
+ addCreateAuditEntry("dhcp6_shared_network", 1);
+ addCreateAuditEntry("dhcp6_shared_network", 2);
+ addCreateAuditEntry("dhcp6_subnet", 1);
+ addCreateAuditEntry("dhcp6_subnet", 2);
+ 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", 1);
+ addCreateAuditEntry("dhcp6_subnet", 2);
+ testDatabaseConfigApply(getTimestamp(-3));
+}
+
+// This test verifies that only client classes are merged into the current
+// configuration.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplyClientClasses) {
+ addCreateAuditEntry("dhcp6_client_class", 1);
+ addCreateAuditEntry("dhcp6_client_class", 2);
+ testDatabaseConfigApply(getTimestamp(-5));
+}
+
+// This test verifies that a client class is deleted from the local
+// configuration as a result of being deleted from the database.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplyDeleteClientClass) {
+ testDatabaseConfigApplyDelete(getTimestamp(-5), [this]() {
+ remoteDeleteClientClass("first-class");
+ });
+}
+
+// This test verifies that the configuration update calls the hook.
+TEST_F(CBControlDHCPv6Test, databaseConfigApplyHook) {
+
+ // Initialize Hooks Manager.
+ HooksManager::loadLibraries(HookLibsCollection());
+
+ // Install cb6_updated.
+ EXPECT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+ "cb6_updated", cb6_updated_callout));
+
+ // Create audit entries.
+ addCreateAuditEntry("dhcp6_global_parameter", 1);
+ addCreateAuditEntry("dhcp6_global_parameter", 2);
+ addCreateAuditEntry("dhcp6_option_def", 1);
+ addCreateAuditEntry("dhcp6_option_def", 2);
+ addCreateAuditEntry("dhcp6_options", 1);
+ addCreateAuditEntry("dhcp6_options", 2);
+ addCreateAuditEntry("dhcp6_shared_network", 1);
+ addCreateAuditEntry("dhcp6_shared_network", 2);
+ addCreateAuditEntry("dhcp6_subnet", 1);
+ addCreateAuditEntry("dhcp6_subnet", 2);
+
+ // Run the test.
+ testDatabaseConfigApply(getTimestamp(-5));
+
+ // Checks the callout.
+ EXPECT_EQ("cb6_updated", callback_name_);
+ ASSERT_TRUE(callback_audit_entries_);
+ EXPECT_TRUE(audit_entries_ == *callback_audit_entries_);
+}
+
+// This test verifies that it is possible to set ip-reservations-unique
+// parameter via configuration backend and that it is successful when
+// host database backend accepts the new setting.
+TEST_F(CBControlDHCPv6Test, ipReservationsNonUniqueAccepted) {
+ // Create host data source which accepts setting non-unique IP
+ // reservations.
+ MemHostDataSourcePtr hds(new MemHostDataSource());
+ auto testFactory = [hds](const DatabaseConnection::ParameterMap&) {
+ return (hds);
+ };
+ HostDataSourceFactory::registerFactory("test", testFactory);
+ HostMgr::addBackend("type=test");
+
+ // Insert ip-reservations-unique value set to false into the database.
+ auto& mgr = ConfigBackendDHCPv6Mgr::instance();
+ StampedValuePtr global_parameter = StampedValue::create("ip-reservations-unique",
+ Element::create(false));
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ global_parameter));
+ // Adding audit entry simulates the case when the server is already configured
+ // and we're adding incremental changes. These changes should be applied to
+ // the current configuration.
+ addCreateAuditEntry("dhcp6_global_parameter", 1);
+
+ // Apply the configuration.
+ ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ getTimestamp(-5),
+ audit_entries_));
+ // The new setting should be visible in both CfgDbAccess and HostMgr.
+ EXPECT_FALSE(CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->getIPReservationsUnique());
+ EXPECT_FALSE(HostMgr::instance().getIPReservationsUnique());
+}
+
+// This test verifies that the new setting of ip-reservations-unique is not
+// accepted when one of the host database backends does not support it.
+TEST_F(CBControlDHCPv6Test, ipReservationsNonUniqueRefused) {
+ // Create host data source which does not accept setting IP reservations
+ // non-unique setting.
+ NonUniqueHostDataSourcePtr hds(new NonUniqueHostDataSource());
+ auto testFactory = [hds](const DatabaseConnection::ParameterMap&) {
+ return (hds);
+ };
+ HostDataSourceFactory::registerFactory("test", testFactory);
+ HostMgr::addBackend("type=test");
+
+ // Insert ip-reservations-unique value set to false into the database.
+ auto& mgr = ConfigBackendDHCPv6Mgr::instance();
+ StampedValuePtr global_parameter = StampedValue::create("ip-reservations-unique",
+ Element::create(false));
+ ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter6(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ global_parameter));
+ // Adding audit entry simulates the case when the server is already configured
+ // and we're adding incremental changes. These changes should be applied to
+ // the current configuration.
+ addCreateAuditEntry("dhcp6_global_parameter", 1);
+
+ // Apply the configuration.
+ ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(),
+ ServerSelector::ALL(),
+ getTimestamp(-5),
+ audit_entries_));
+ // The default setting should be applied, because the backend refused to
+ // set it to false.
+ EXPECT_TRUE(CfgMgr::instance().getCurrentCfg()->getCfgDbAccess()->getIPReservationsUnique());
+ EXPECT_TRUE(HostMgr::instance().getIPReservationsUnique());
+}
+
+}