From: Marcin Siodelski Date: Wed, 5 Apr 2023 08:28:26 +0000 (+0200) Subject: [#2823] Config backend sets allocators X-Git-Tag: Kea-2.3.7~27 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5abd6990ce0c98a2ffc35804e414e6a4633b93be;p=thirdparty%2Fkea.git [#2823] Config backend sets allocators --- diff --git a/src/lib/dhcpsrv/allocator.cc b/src/lib/dhcpsrv/allocator.cc index 392c12c65d..5925689d20 100644 --- a/src/lib/dhcpsrv/allocator.cc +++ b/src/lib/dhcpsrv/allocator.cc @@ -7,6 +7,7 @@ #include #include #include +#include using namespace isc::util; @@ -14,7 +15,8 @@ namespace isc { namespace dhcp { Allocator::Allocator(Lease::Type type, const WeakSubnetPtr& subnet) - : pool_type_(type), + : inited_(false), + pool_type_(type), subnet_id_(0), subnet_(subnet) { // Remember subnet ID in a separate variable. It may be needed in @@ -63,5 +65,19 @@ Allocator::isValidPrefixPool(Allocator::PrefixLenMatchType prefix_length_match, return (true); } +void +Allocator::initAfterConfigure() { + if (inited_) { + return; + } + auto subnet = subnet_.lock(); + LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_USE_ALLOCATOR) + .arg(getType()) + .arg(Lease::typeToText(pool_type_)) + .arg(subnet->toText()); + initAfterConfigureInternal(); + inited_ = true; +} + } } diff --git a/src/lib/dhcpsrv/allocator.h b/src/lib/dhcpsrv/allocator.h index f8dcdf253e..a94bd81a2d 100644 --- a/src/lib/dhcpsrv/allocator.h +++ b/src/lib/dhcpsrv/allocator.h @@ -78,6 +78,11 @@ public: /// Removes all LeaseMgr callbacks it installed. virtual ~Allocator(); + /// @brief Returns allocator type string. + /// + /// @return allocator-specific type string. + virtual std::string getType() const = 0; + /// @brief Picks an address. /// /// This method returns one address from the available pools in the @@ -156,7 +161,15 @@ public: /// reconfiguration). Such callbacks can be installed in this function. /// /// In this function, the allocators can also re-build their allocation states. - virtual void initAfterConfigure() {}; + void initAfterConfigure(); + +protected: + + /// @brief Allocator-specific initialization function. + /// + /// It is called by the @c initAfterConfigure and can be overridden in the + /// derived allocators. + virtual void initAfterConfigureInternal() {}; private: @@ -203,6 +216,12 @@ private: protected: + /// @brief Indicates if the allocator has been initialized. + /// + /// It is set to true when @c initAfterConfigure has been called. + /// It prevents initializing the allocator several times. + bool inited_; + /// @brief Defines pool type allocation Lease::Type pool_type_; diff --git a/src/lib/dhcpsrv/cb_ctl_dhcp4.cc b/src/lib/dhcpsrv/cb_ctl_dhcp4.cc index 058530e541..ca1c9d7366 100644 --- a/src/lib/dhcpsrv/cb_ctl_dhcp4.cc +++ b/src/lib/dhcpsrv/cb_ctl_dhcp4.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2019-2023 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 @@ -48,7 +48,11 @@ CBControlDHCPv4::databaseConfigApply(const BackendSelector& backend_selector, const boost::posix_time::ptime& lb_modification_time, const AuditEntryCollection& audit_entries) { - bool globals_fetched = false; + auto globals_fetched = false; + auto reconfig = audit_entries.empty(); + auto cb_update = !reconfig; + auto current_cfg = CfgMgr::instance().getCurrentCfg(); + auto staging_cfg = CfgMgr::instance().getStagingCfg(); // Let's first delete all the configuration elements for which DELETE audit // entries are found. Although, this may break chronology of the audit in @@ -57,9 +61,8 @@ CBControlDHCPv4::databaseConfigApply(const BackendSelector& backend_selector, // 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()) { + if (cb_update) { - auto cfg = CfgMgr::instance().getCurrentCfg(); auto external_cfg = CfgMgr::instance().createExternalCfg(); // Get audit entries for deleted global parameters. @@ -85,7 +88,7 @@ CBControlDHCPv4::databaseConfigApply(const BackendSelector& backend_selector, // Now that we successfully fetched the new global parameters, let's // remove existing ones and merge them into the current configuration. - cfg->clearConfiguredGlobals(); + current_cfg->clearConfiguredGlobals(); CfgMgr::instance().mergeIntoCurrentCfg(external_cfg->getSequence()); globals_fetched = true; } @@ -97,7 +100,7 @@ CBControlDHCPv4::databaseConfigApply(const BackendSelector& backend_selector, range = index.equal_range(boost::make_tuple("dhcp4_option_def", AuditEntry::ModificationType::DELETE)); for (auto entry = range.first; entry != range.second; ++entry) { - cfg->getCfgOptionDef()->del((*entry)->getObjectId()); + current_cfg->getCfgOptionDef()->del((*entry)->getObjectId()); } // Repeat the same for other configuration elements. @@ -105,19 +108,19 @@ CBControlDHCPv4::databaseConfigApply(const BackendSelector& backend_selector, range = index.equal_range(boost::make_tuple("dhcp4_options", AuditEntry::ModificationType::DELETE)); for (auto entry = range.first; entry != range.second; ++entry) { - cfg->getCfgOption()->del((*entry)->getObjectId()); + current_cfg->getCfgOption()->del((*entry)->getObjectId()); } range = index.equal_range(boost::make_tuple("dhcp4_client_class", AuditEntry::ModificationType::DELETE)); for (auto entry = range.first; entry != range.second; ++entry) { - cfg->getClientClassDictionary()->removeClass((*entry)->getObjectId()); + current_cfg->getClientClassDictionary()->removeClass((*entry)->getObjectId()); } range = index.equal_range(boost::make_tuple("dhcp4_shared_network", AuditEntry::ModificationType::DELETE)); for (auto entry = range.first; entry != range.second; ++entry) { - cfg->getCfgSharedNetworks4()->del((*entry)->getObjectId()); + current_cfg->getCfgSharedNetworks4()->del((*entry)->getObjectId()); } range = index.equal_range(boost::make_tuple("dhcp4_subnet", @@ -126,7 +129,7 @@ CBControlDHCPv4::databaseConfigApply(const BackendSelector& backend_selector, // 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->getCfgSubnets4()->getBySubnetId((*entry)->getObjectId()); + auto subnet = current_cfg->getCfgSubnets4()->getBySubnetId((*entry)->getObjectId()); if (subnet) { // Check if the subnet belongs to a shared network. SharedNetwork4Ptr network; @@ -136,7 +139,7 @@ CBControlDHCPv4::databaseConfigApply(const BackendSelector& backend_selector, network->del(subnet->getID()); } // Actually delete the subnet from the configuration. - cfg->getCfgSubnets4()->del((*entry)->getObjectId()); + current_cfg->getCfgSubnets4()->del((*entry)->getObjectId()); } } @@ -149,26 +152,28 @@ CBControlDHCPv4::databaseConfigApply(const BackendSelector& backend_selector, } // Create the external config into which we'll fetch backend config data. - SrvConfigPtr external_cfg = CfgMgr::instance().createExternalCfg(); + auto external_cfg = CfgMgr::instance().createExternalCfg(); // First let's fetch the globals and add them to external config. AuditEntryCollection updated_entries; - if (!globals_fetched && !audit_entries.empty()) { - updated_entries = fetchConfigElement(audit_entries, "dhcp4_global_parameter"); - } - if (!globals_fetched && (audit_entries.empty() || !updated_entries.empty())) { - data::StampedValueCollection globals; - globals = getMgr().getPool()->getModifiedGlobalParameters4(backend_selector, server_selector, - lb_modification_time); - addGlobalsToConfig(external_cfg, globals); - globals_fetched = true; + if (!globals_fetched) { + if (cb_update) { + updated_entries = fetchConfigElement(audit_entries, "dhcp4_global_parameter"); + } + if (reconfig || !updated_entries.empty()) { + data::StampedValueCollection globals; + globals = getMgr().getPool()->getModifiedGlobalParameters4(backend_selector, server_selector, + lb_modification_time); + addGlobalsToConfig(external_cfg, globals); + globals_fetched = true; + } } // Now we fetch the option definitions and add them. - if (!audit_entries.empty()) { + if (cb_update) { updated_entries = fetchConfigElement(audit_entries, "dhcp4_option_def"); } - if (audit_entries.empty() || !updated_entries.empty()) { + if (reconfig || !updated_entries.empty()) { OptionDefContainer option_defs = getMgr().getPool()->getModifiedOptionDefs4(backend_selector, server_selector, lb_modification_time); @@ -181,10 +186,10 @@ CBControlDHCPv4::databaseConfigApply(const BackendSelector& backend_selector, } // Next fetch the options. They are returned as a container of OptionDescriptors. - if (!audit_entries.empty()) { + if (cb_update) { updated_entries = fetchConfigElement(audit_entries, "dhcp4_options"); } - if (audit_entries.empty() || !updated_entries.empty()) { + if (reconfig || !updated_entries.empty()) { OptionContainer options = getMgr().getPool()->getModifiedOptions4(backend_selector, server_selector, lb_modification_time); @@ -197,10 +202,10 @@ CBControlDHCPv4::databaseConfigApply(const BackendSelector& backend_selector, } // Fetch client classes. They are returned in a ClientClassDictionary. - if (!audit_entries.empty()) { + if (cb_update) { updated_entries = fetchConfigElement(audit_entries, "dhcp4_client_class"); } - if (audit_entries.empty() || !updated_entries.empty()) { + if (reconfig || !updated_entries.empty()) { ClientClassDictionary client_classes = getMgr().getPool()->getAllClientClasses4(backend_selector, server_selector); // Match expressions are not initialized for classes returned from the config backend. @@ -213,57 +218,99 @@ CBControlDHCPv4::databaseConfigApply(const BackendSelector& backend_selector, external_cfg->setClientClassDictionary(boost::make_shared(client_classes)); } + // Allocator selection at the global level can affect subnets and shared networks + // for which the allocator hasn't been specified explicitly. Let's see if the + // allocator has been specified at the global level. + std::string default_allocator; + auto allocator = external_cfg->getConfiguredGlobal(CfgGlobals::ALLOCATOR); + if (allocator && (allocator->getType() == Element::string)) { + default_allocator = allocator->stringValue(); + } + + // If we're fetching the changes from the config backend we also want + // to see if the global allocator has changed. Let's get the currently + // used allocator too. + auto allocator_changed = false; + // We're only affected by the allocator change if this is the update from + // the configuration backend. + if (cb_update) { + std::string current_default_allocator; + auto allocator = CfgMgr::instance().getCurrentCfg()->getConfiguredGlobal(CfgGlobals::ALLOCATOR); + if (allocator && (allocator->getType() == Element::string)) { + allocator_changed = (default_allocator != allocator->stringValue()); + } + } + // Now fetch the shared networks. - if (!audit_entries.empty()) { + if (cb_update) { updated_entries = fetchConfigElement(audit_entries, "dhcp4_shared_network"); } - if (audit_entries.empty() || !updated_entries.empty()) { - SharedNetwork4Collection networks = - getMgr().getPool()->getModifiedSharedNetworks4(backend_selector, server_selector, - lb_modification_time); - for (auto network = networks.begin(); network != networks.end(); ++network) { - if (!audit_entries.empty() && !hasObjectId(updated_entries, (*network)->getId())) { - continue; - } - // 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([] () -> ConstCfgGlobalsPtr { - return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals()); - }); - external_cfg->getCfgSharedNetworks4()->add((*network)); + SharedNetwork4Collection networks; + if (allocator_changed || reconfig) { + // A change of the allocator or the server reconfiguration can affect all + // shared networks. Get all shared networks. + networks = getMgr().getPool()->getAllSharedNetworks4(backend_selector, server_selector); + + } else if (!updated_entries.empty()) { + // An update from the config backend when the global allocator hasn't changed + // means that we only need to handle the modified subnets. + networks = getMgr().getPool()->getModifiedSharedNetworks4(backend_selector, server_selector, + lb_modification_time); + } + // Iterate over all shared networks that may require reconfiguration. + for (auto network = networks.begin(); network != networks.end(); ++network) { + if (!allocator_changed && cb_update && !hasObjectId(updated_entries, (*network)->getId())) { + continue; } + // 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([] () -> ConstCfgGlobalsPtr { + return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals()); + }); + (*network)->setDefaultAllocatorType(default_allocator); + external_cfg->getCfgSharedNetworks4()->add((*network)); } - // Next we fetch subnets. - if (!audit_entries.empty()) { + // Next, fetch the subnets. + if (cb_update) { updated_entries = fetchConfigElement(audit_entries, "dhcp4_subnet"); } - if (audit_entries.empty() || !updated_entries.empty()) { - Subnet4Collection subnets = getMgr().getPool()->getModifiedSubnets4(backend_selector, - server_selector, - lb_modification_time); - for (auto subnet = subnets.begin(); subnet != subnets.end(); ++subnet) { - if (!audit_entries.empty() && !hasObjectId(updated_entries, (*subnet)->getID())) { - continue; - } - // 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([] () -> ConstCfgGlobalsPtr { - return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals()); - }); - external_cfg->getCfgSubnets4()->add((*subnet)); + Subnet4Collection subnets; + if (allocator_changed || reconfig) { + // A change of the allocator or the server reconfiguration can affect all + // shared networks. Get all subnets. + subnets = getMgr().getPool()->getAllSubnets4(backend_selector, server_selector); + + } else if (!updated_entries.empty()) { + // An update from the config backend when the global allocator hasn't changed + // means that we only need to handle the modified subnets. + subnets = getMgr().getPool()->getModifiedSubnets4(backend_selector, + server_selector, + lb_modification_time); + } + // Iterate over all subnets that may require reconfiguration. + for (auto subnet = subnets.begin(); subnet != subnets.end(); ++subnet) { + if (!allocator_changed && cb_update && !hasObjectId(updated_entries, (*subnet)->getID())) { + continue; } + // 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([] () -> ConstCfgGlobalsPtr { + return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals()); + }); + (*subnet)->setDefaultAllocatorType(default_allocator); + external_cfg->getCfgSubnets4()->add((*subnet)); } - if (audit_entries.empty()) { + if (reconfig) { // If we're configuring the server after startup, we do not apply the // ip-reservations-unique setting here. It will be applied when the // configuration is committed. - auto const& cfg = CfgMgr::instance().getStagingCfg(); - external_cfg->sanityChecksLifetime(*cfg, "valid-lifetime"); + external_cfg->sanityChecksLifetime(*staging_cfg, "valid-lifetime"); CfgMgr::instance().mergeIntoStagingCfg(external_cfg->getSequence()); + } else { if (globals_fetched) { // ip-reservations-unique parameter requires special handling because @@ -282,13 +329,14 @@ CBControlDHCPv4::databaseConfigApply(const BackendSelector& backend_selector, external_cfg->addConfiguredGlobal("ip-reservations-unique", Element::create(true)); } } - auto const& cfg = CfgMgr::instance().getCurrentCfg(); - external_cfg->sanityChecksLifetime(*cfg, "valid-lifetime"); + external_cfg->sanityChecksLifetime(*current_cfg, "valid-lifetime"); CfgMgr::instance().mergeIntoCurrentCfg(external_cfg->getSequence()); + CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->initAllocatorsAfterConfigure(); } + LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_CONFIG4_MERGED); - if (!audit_entries.empty() && + if (cb_update && HooksManager::calloutsPresent(hooks_.hook_index_cb4_updated_)) { CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); diff --git a/src/lib/dhcpsrv/cb_ctl_dhcp6.cc b/src/lib/dhcpsrv/cb_ctl_dhcp6.cc index 1278d9409e..4624bf495b 100644 --- a/src/lib/dhcpsrv/cb_ctl_dhcp6.cc +++ b/src/lib/dhcpsrv/cb_ctl_dhcp6.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2019-2022 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2019-2023 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 @@ -47,6 +47,10 @@ CBControlDHCPv6::databaseConfigApply(const db::BackendSelector& backend_selector const boost::posix_time::ptime& lb_modification_time, const db::AuditEntryCollection& audit_entries) { bool globals_fetched = false; + auto reconfig = audit_entries.empty(); + auto cb_update = !reconfig; + auto current_cfg = CfgMgr::instance().getCurrentCfg(); + auto staging_cfg = CfgMgr::instance().getStagingCfg(); // Let's first delete all the configuration elements for which DELETE audit // entries are found. Although, this may break chronology of the audit in @@ -55,9 +59,8 @@ CBControlDHCPv6::databaseConfigApply(const db::BackendSelector& backend_selector // 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()) { + if (cb_update) { - auto cfg = CfgMgr::instance().getCurrentCfg(); auto external_cfg = CfgMgr::instance().createExternalCfg(); // Get audit entries for deleted global parameters. @@ -84,7 +87,7 @@ CBControlDHCPv6::databaseConfigApply(const db::BackendSelector& backend_selector // Now that we successfully fetched the new global parameters, let's // remove existing ones and merge them into the current configuration. - cfg->clearConfiguredGlobals(); + current_cfg->clearConfiguredGlobals(); CfgMgr::instance().mergeIntoCurrentCfg(external_cfg->getSequence()); globals_fetched = true; } @@ -96,7 +99,7 @@ CBControlDHCPv6::databaseConfigApply(const db::BackendSelector& backend_selector 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()); + current_cfg->getCfgOptionDef()->del((*entry)->getObjectId()); } // Repeat the same for other configuration elements. @@ -104,19 +107,19 @@ CBControlDHCPv6::databaseConfigApply(const db::BackendSelector& backend_selector 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()); + current_cfg->getCfgOption()->del((*entry)->getObjectId()); } range = index.equal_range(boost::make_tuple("dhcp6_client_class", AuditEntry::ModificationType::DELETE)); for (auto entry = range.first; entry != range.second; ++entry) { - cfg->getClientClassDictionary()->removeClass((*entry)->getObjectId()); + current_cfg->getClientClassDictionary()->removeClass((*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()); + current_cfg->getCfgSharedNetworks6()->del((*entry)->getObjectId()); } range = index.equal_range(boost::make_tuple("dhcp6_subnet", @@ -125,7 +128,7 @@ CBControlDHCPv6::databaseConfigApply(const db::BackendSelector& backend_selector // 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()); + auto subnet = current_cfg->getCfgSubnets6()->getBySubnetId((*entry)->getObjectId()); if (subnet) { // Check if the subnet belongs to a shared network. SharedNetwork6Ptr network; @@ -135,7 +138,7 @@ CBControlDHCPv6::databaseConfigApply(const db::BackendSelector& backend_selector network->del(subnet->getID()); } // Actually delete the subnet from the configuration. - cfg->getCfgSubnets6()->del((*entry)->getObjectId()); + current_cfg->getCfgSubnets6()->del((*entry)->getObjectId()); } } @@ -152,22 +155,24 @@ CBControlDHCPv6::databaseConfigApply(const db::BackendSelector& backend_selector // First let's fetch the globals and add them to external config. AuditEntryCollection updated_entries; - if (!globals_fetched && !audit_entries.empty()) { - updated_entries = fetchConfigElement(audit_entries, "dhcp6_global_parameter"); - } - if (!globals_fetched && (audit_entries.empty() || !updated_entries.empty())) { - data::StampedValueCollection globals; - globals = getMgr().getPool()->getModifiedGlobalParameters6(backend_selector, server_selector, - lb_modification_time); - addGlobalsToConfig(external_cfg, globals); - globals_fetched = true; + if (!globals_fetched) { + if (cb_update) { + updated_entries = fetchConfigElement(audit_entries, "dhcp6_global_parameter"); + } + if (reconfig || !updated_entries.empty()) { + data::StampedValueCollection globals; + globals = getMgr().getPool()->getModifiedGlobalParameters6(backend_selector, server_selector, + lb_modification_time); + addGlobalsToConfig(external_cfg, globals); + globals_fetched = true; + } } // Now we fetch the option definitions and add them. - if (!audit_entries.empty()) { + if (cb_update) { updated_entries = fetchConfigElement(audit_entries, "dhcp6_option_def"); } - if (audit_entries.empty() || !updated_entries.empty()) { + if (reconfig || !updated_entries.empty()) { OptionDefContainer option_defs = getMgr().getPool()->getModifiedOptionDefs6(backend_selector, server_selector, lb_modification_time); @@ -180,10 +185,10 @@ CBControlDHCPv6::databaseConfigApply(const db::BackendSelector& backend_selector } // Next fetch the options. They are returned as a container of OptionDescriptors. - if (!audit_entries.empty()) { + if (cb_update) { updated_entries = fetchConfigElement(audit_entries, "dhcp6_options"); } - if (audit_entries.empty() || !updated_entries.empty()) { + if (reconfig || !updated_entries.empty()) { OptionContainer options = getMgr().getPool()->getModifiedOptions6(backend_selector, server_selector, lb_modification_time); @@ -196,10 +201,10 @@ CBControlDHCPv6::databaseConfigApply(const db::BackendSelector& backend_selector } // Fetch client classes. They are returned in a ClientClassDictionary. - if (!audit_entries.empty()) { + if (cb_update) { updated_entries = fetchConfigElement(audit_entries, "dhcp6_client_class"); } - if (audit_entries.empty() || !updated_entries.empty()) { + if (reconfig || !updated_entries.empty()) { ClientClassDictionary client_classes = getMgr().getPool()->getAllClientClasses6(backend_selector, server_selector); // Match expressions are not initialized for classes returned from the config backend. @@ -212,51 +217,106 @@ CBControlDHCPv6::databaseConfigApply(const db::BackendSelector& backend_selector external_cfg->setClientClassDictionary(boost::make_shared(client_classes)); } + // Allocator selection at the global level can affect subnets and shared networks + // for which the allocator hasn't been specified explicitly. Let's see if the + // allocator has been specified at the global level. + std::string default_allocator; + auto allocator = external_cfg->getConfiguredGlobal(CfgGlobals::ALLOCATOR); + if (allocator && (allocator->getType() == Element::string)) { + default_allocator = allocator->stringValue(); + } + + // Also, get the PD allocator. + std::string default_pd_allocator; + allocator = external_cfg->getConfiguredGlobal(CfgGlobals::PD_ALLOCATOR); + if (allocator && (allocator->getType() == Element::string)) { + default_pd_allocator = allocator->stringValue(); + } + + // If we're fetching the changes from the config backend we also want + // to see if the global allocator has changed. Let's get the currently + // used allocator too. + auto allocator_changed = false; + // We're only affected by the allocator change if this is the update from + // the configuration backend. + if (cb_update) { + auto allocator = CfgMgr::instance().getCurrentCfg()->getConfiguredGlobal(CfgGlobals::ALLOCATOR); + if (allocator && (allocator->getType() == Element::string)) { + allocator_changed = (default_allocator != allocator->stringValue()); + } + + // The address allocator hasn't changed. So, let's check if the PD allocator + // has changed. + if (!allocator_changed) { + auto allocator = CfgMgr::instance().getCurrentCfg()->getConfiguredGlobal(CfgGlobals::PD_ALLOCATOR); + if (allocator && (allocator->getType() == Element::string)) { + allocator_changed = (default_pd_allocator != allocator->stringValue()); + } + } + } + // Now fetch the shared networks. - if (!audit_entries.empty()) { + if (cb_update) { updated_entries = fetchConfigElement(audit_entries, "dhcp6_shared_network"); } - if (audit_entries.empty() || !updated_entries.empty()) { - SharedNetwork6Collection networks = - getMgr().getPool()->getModifiedSharedNetworks6(backend_selector, server_selector, - lb_modification_time); - for (auto network = networks.begin(); network != networks.end(); ++network) { - if (!audit_entries.empty() && !hasObjectId(updated_entries, (*network)->getId())) { - continue; - } - // 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([] () -> ConstCfgGlobalsPtr { - return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals()); - }); - external_cfg->getCfgSharedNetworks6()->add((*network)); + SharedNetwork6Collection networks; + if (allocator_changed || reconfig) { + // A change of the allocator or the server reconfiguration can affect all + // shared networks. Get all shared networks. + networks = getMgr().getPool()->getAllSharedNetworks6(backend_selector, server_selector); + } else if (!updated_entries.empty()) { + networks = getMgr().getPool()->getModifiedSharedNetworks6(backend_selector, server_selector, + lb_modification_time); + } + for (auto network = networks.begin(); network != networks.end(); ++network) { + if (!allocator_changed && cb_update && !hasObjectId(updated_entries, (*network)->getId())) { + continue; } + // 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([] () -> ConstCfgGlobalsPtr { + return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals()); + }); + (*network)->setDefaultAllocatorType(default_allocator); + (*network)->setDefaultPdAllocatorType(default_pd_allocator); + external_cfg->getCfgSharedNetworks6()->add((*network)); } // Next we fetch subnets. - if (!audit_entries.empty()) { + if (cb_update) { updated_entries = fetchConfigElement(audit_entries, "dhcp6_subnet"); } - if (audit_entries.empty() || !updated_entries.empty()) { - Subnet6Collection subnets = getMgr().getPool()->getModifiedSubnets6(backend_selector, - server_selector, - lb_modification_time); - for (auto subnet = subnets.begin(); subnet != subnets.end(); ++subnet) { - if (!audit_entries.empty() && !hasObjectId(updated_entries, (*subnet)->getID())) { - continue; - } - // 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([] () -> ConstCfgGlobalsPtr { - return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals()); - }); - external_cfg->getCfgSubnets6()->add((*subnet)); + Subnet6Collection subnets; + if (allocator_changed || reconfig) { + // A change of the allocator or the server reconfiguration can affect all + // shared networks. Get all subnets. + subnets = getMgr().getPool()->getAllSubnets6(backend_selector, server_selector); + + } else if (!updated_entries.empty()) { + // An update from the config backend when the global allocator hasn't changed + // means that we only need to handle the modified subnets. + subnets = getMgr().getPool()->getModifiedSubnets6(backend_selector, + server_selector, + lb_modification_time); + } + // Iterate over all subnets that may require reconfiguration. + for (auto subnet = subnets.begin(); subnet != subnets.end(); ++subnet) { + if (!audit_entries.empty() && !hasObjectId(updated_entries, (*subnet)->getID())) { + continue; } + // 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([] () -> ConstCfgGlobalsPtr { + return (CfgMgr::instance().getCurrentCfg()->getConfiguredGlobals()); + }); + (*subnet)->setDefaultAllocatorType(default_allocator); + (*subnet)->setDefaultPdAllocatorType(default_pd_allocator); + external_cfg->getCfgSubnets6()->add((*subnet)); } - if (audit_entries.empty()) { + if (reconfig) { // If we're configuring the server after startup, we do not apply the // ip-reservations-unique setting here. It will be applied when the // configuration is committed. @@ -264,6 +324,7 @@ CBControlDHCPv6::databaseConfigApply(const db::BackendSelector& backend_selector external_cfg->sanityChecksLifetime(*cfg, "preferred-lifetime"); external_cfg->sanityChecksLifetime(*cfg, "valid-lifetime"); CfgMgr::instance().mergeIntoStagingCfg(external_cfg->getSequence()); + } else { if (globals_fetched) { // ip-reservations-unique parameter requires special handling because @@ -286,10 +347,11 @@ CBControlDHCPv6::databaseConfigApply(const db::BackendSelector& backend_selector external_cfg->sanityChecksLifetime(*cfg, "preferred-lifetime"); external_cfg->sanityChecksLifetime(*cfg, "valid-lifetime"); CfgMgr::instance().mergeIntoCurrentCfg(external_cfg->getSequence()); + CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->initAllocatorsAfterConfigure(); } LOG_INFO(dhcpsrv_logger, DHCPSRV_CFGMGR_CONFIG6_MERGED); - if (!audit_entries.empty() && + if (cb_update && HooksManager::calloutsPresent(hooks_.hook_index_cb6_updated_)) { CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle(); diff --git a/src/lib/dhcpsrv/cfg_subnets4.cc b/src/lib/dhcpsrv/cfg_subnets4.cc index aba88116f1..9d017426e3 100644 --- a/src/lib/dhcpsrv/cfg_subnets4.cc +++ b/src/lib/dhcpsrv/cfg_subnets4.cc @@ -178,6 +178,8 @@ CfgSubnets4::merge(CfgOptionDefPtr cfg_def, CfgSharedNetworks4Ptr networks, << ", network does not exist"); } } + // Instantiate the configured allocator and its state. + other_subnet->createAllocators(); } } diff --git a/src/lib/dhcpsrv/cfg_subnets6.cc b/src/lib/dhcpsrv/cfg_subnets6.cc index a86ee9222c..9632d2f88c 100644 --- a/src/lib/dhcpsrv/cfg_subnets6.cc +++ b/src/lib/dhcpsrv/cfg_subnets6.cc @@ -178,6 +178,8 @@ CfgSubnets6::merge(CfgOptionDefPtr cfg_def, CfgSharedNetworks6Ptr networks, << ", network does not exist"); } } + // Instantiate the configured allocators and their states. + other_subnet->createAllocators(); } } diff --git a/src/lib/dhcpsrv/dhcpsrv_messages.mes b/src/lib/dhcpsrv/dhcpsrv_messages.mes index f9f15d034e..1f3dc0737c 100644 --- a/src/lib/dhcpsrv/dhcpsrv_messages.mes +++ b/src/lib/dhcpsrv/dhcpsrv_messages.mes @@ -265,6 +265,10 @@ specified IPv6 subnet in its current configuration. Subnet ID and result A message issued when the server is configured to listen on the explicitly specified IP address on the given interface. +% DHCPSRV_CFGMGR_USE_ALLOCATOR using the %1 allocator for %2 leases in subnet %3 +A message issued when the configuration manager starts using a given allocator +for a subnet. + % DHCPSRV_CFGMGR_USE_UNICAST listening on unicast address %1, on interface %2 An info message issued when configuring the DHCP server to listen on the unicast address on the specific interface. diff --git a/src/lib/dhcpsrv/flq_allocator.cc b/src/lib/dhcpsrv/flq_allocator.cc index b1265559b4..0ce83ff2fc 100644 --- a/src/lib/dhcpsrv/flq_allocator.cc +++ b/src/lib/dhcpsrv/flq_allocator.cc @@ -135,7 +135,7 @@ FreeLeaseQueueAllocator::pickPrefixInternal(const ClientClasses& client_classes, } void -FreeLeaseQueueAllocator::initAfterConfigure() { +FreeLeaseQueueAllocator::initAfterConfigureInternal() { auto subnet = subnet_.lock(); auto pools = subnet->getPools(pool_type_); if (pools.empty()) { diff --git a/src/lib/dhcpsrv/flq_allocator.h b/src/lib/dhcpsrv/flq_allocator.h index b95b4d8260..e3b9cf89ab 100644 --- a/src/lib/dhcpsrv/flq_allocator.h +++ b/src/lib/dhcpsrv/flq_allocator.h @@ -44,13 +44,20 @@ public: /// @param subnet weak pointer to the subnet owning the allocator. FreeLeaseQueueAllocator(Lease::Type type, const WeakSubnetPtr& subnet); + /// @brief Returns the allocator type string. + /// + /// @return flq string. + virtual std::string getType() const { + return ("flq"); + } + +private: + /// @brief Performs allocator initialization after server's reconfiguration. /// /// The allocator installs the callbacks in the lease manager to keep track of /// the lease allocations and maintain the free leases queue. - virtual void initAfterConfigure(); - -private: + virtual void initAfterConfigureInternal(); /// @brief Populates the queue of free addresses (IPv4 and IPv6). /// diff --git a/src/lib/dhcpsrv/iterative_allocator.h b/src/lib/dhcpsrv/iterative_allocator.h index fafcc64789..7be2ea9725 100644 --- a/src/lib/dhcpsrv/iterative_allocator.h +++ b/src/lib/dhcpsrv/iterative_allocator.h @@ -31,6 +31,13 @@ public: /// @param subnet weak pointer to the subnet owning the allocator. IterativeAllocator(Lease::Type type, const WeakSubnetPtr& subnet); + /// @brief Returns the allocator type string. + /// + /// @return iterative string. + virtual std::string getType() const { + return ("iterative"); + } + private: /// @brief Returns the next address from the pools in the subnet. diff --git a/src/lib/dhcpsrv/network.h b/src/lib/dhcpsrv/network.h index 147f79e826..3eda94f6a3 100644 --- a/src/lib/dhcpsrv/network.h +++ b/src/lib/dhcpsrv/network.h @@ -219,7 +219,8 @@ public: ddns_replace_client_name_mode_(), ddns_generated_prefix_(), ddns_qualifying_suffix_(), hostname_char_set_(), hostname_char_replacement_(), store_extended_info_(), cache_threshold_(), cache_max_age_(), ddns_update_on_renew_(), - ddns_use_conflict_resolution_(), ddns_ttl_percent_() { + ddns_use_conflict_resolution_(), ddns_ttl_percent_(), allocator_type_(), + default_allocator_type_() { } /// @brief Virtual destructor. @@ -818,6 +819,26 @@ public: allocator_type_ = allocator_type; } + /// @brief Returns a default allocator type. + /// + /// This allocator type is used when the allocator type is neither specified + /// at the shared network nor subnet level. + /// + /// @return an allocator type as a string. + util::Optional + getDefaultAllocatorType(const Inheritance& inheritance = Inheritance::ALL) const { + return (getProperty(&Network::getDefaultAllocatorType, + default_allocator_type_, + inheritance)); + } + + /// @brief Sets a defalt allocator type. + /// + /// @param allocator_type a new default allocator type. + void setDefaultAllocatorType(const std::string& allocator_type) { + default_allocator_type_ = allocator_type; + } + /// @brief Unparses network object. /// /// @return A pointer to unparsed network configuration. @@ -1209,11 +1230,14 @@ protected: /// @brief Used to to tell kea-dhcp-ddns whether or not to use conflict resolution. util::Optional ddns_use_conflict_resolution_; + /// @brief Percentage of the lease lifetime to use for DNS TTL. + util::Optional ddns_ttl_percent_; + /// @brief Allocator used for IP address allocations. util::Optional allocator_type_; - /// @brief Percentage of the lease lifetime to use for DNS TTL. - util::Optional ddns_ttl_percent_; + /// @brief Default allocator type. + util::Optional default_allocator_type_; /// @brief Pointer to another network that this network belongs to. /// @@ -1395,7 +1419,8 @@ public: /// @brief Constructor. Network6() - : Network(), preferred_(), interface_id_(), rapid_commit_() { + : Network(), preferred_(), interface_id_(), rapid_commit_(), + default_pd_allocator_type_(){ } /// @brief Returns preferred lifetime (in seconds) @@ -1478,6 +1503,26 @@ public: pd_allocator_type_ = allocator_type; } + /// @brief Returns a default allocator type for prefix delegation. + /// + /// This allocator type is used when the allocator type is neither specified + /// at the shared network nor subnet level. + /// + /// @return an allocator type as a string. + util::Optional + getDefaultPdAllocatorType(const Inheritance& inheritance = Inheritance::ALL) const { + return (getProperty(&Network6::getDefaultPdAllocatorType, + default_pd_allocator_type_, + inheritance)); + } + + /// @brief Sets a defalt allocator type for prefix delegation. + /// + /// @param allocator_type a new default allocator type. + void setDefaultPdAllocatorType(const std::string& allocator_type) { + default_pd_allocator_type_ = allocator_type; + } + /// @brief Unparses network object. /// /// @return A pointer to unparsed network configuration. @@ -1500,6 +1545,9 @@ private: /// @brief Allocator used for prefix delegation. util::Optional pd_allocator_type_; + + // @brief Default allocator type for prefix delegation. + util::Optional default_pd_allocator_type_; }; } // end of namespace isc::dhcp diff --git a/src/lib/dhcpsrv/random_allocator.h b/src/lib/dhcpsrv/random_allocator.h index 04a0ad4d78..9064830297 100644 --- a/src/lib/dhcpsrv/random_allocator.h +++ b/src/lib/dhcpsrv/random_allocator.h @@ -36,6 +36,13 @@ public: /// @param subnet weak pointer to the subnet owning the allocator. RandomAllocator(Lease::Type type, const WeakSubnetPtr& subnet); + /// @brief Returns the allocator type string. + /// + /// @return random string. + virtual std::string getType() const { + return ("random"); + } + private: /// @brief Returns a random address from the pools in the subnet. diff --git a/src/lib/dhcpsrv/subnet.cc b/src/lib/dhcpsrv/subnet.cc index dbd7108107..e6038bb932 100644 --- a/src/lib/dhcpsrv/subnet.cc +++ b/src/lib/dhcpsrv/subnet.cc @@ -750,7 +750,11 @@ Subnet::toElement() const { void Subnet4::createAllocators() { - if (getAllocatorType() == "random") { + auto allocator_type = getAllocatorType(); + if (allocator_type.empty()) { + allocator_type = getDefaultAllocatorType(); + } + if (allocator_type == "random") { setAllocator(Lease::TYPE_V4, boost::make_shared (Lease::TYPE_V4, shared_from_this())); @@ -760,7 +764,7 @@ Subnet4::createAllocators() { pool->setAllocationState(PoolRandomAllocationState::create(pool)); } - } else if (getAllocatorType() == "flq") { + } else if (allocator_type == "flq") { setAllocator(Lease::TYPE_V4, boost::make_shared (Lease::TYPE_V4, shared_from_this())); @@ -769,6 +773,9 @@ Subnet4::createAllocators() { } } else { + setAllocator(Lease::TYPE_V4, + boost::make_shared + (Lease::TYPE_V4, shared_from_this())); for (auto pool : pools_) { pool->setAllocationState(PoolIterativeAllocationState::create(pool)); } @@ -811,10 +818,11 @@ Subnet4::parsePrefix(const std::string& prefix) { void Subnet6::createAllocators() { - // If we use the random allocator we need to create its instance and - // the state instance for it. There is no need to do it for the iterative - // allocator because it is configured by default. - if (getAllocatorType() == "random") { + auto allocator_type = getAllocatorType(); + if (allocator_type.empty()) { + allocator_type = getDefaultAllocatorType(); + } + if (allocator_type == "random") { setAllocator(Lease::TYPE_NA, boost::make_shared (Lease::TYPE_NA, shared_from_this())); @@ -824,26 +832,43 @@ Subnet6::createAllocators() { setAllocationState(Lease::TYPE_NA, SubnetAllocationStatePtr()); setAllocationState(Lease::TYPE_TA, SubnetAllocationStatePtr()); - } else if (getAllocatorType() == "flq") { + } else if (allocator_type == "flq") { isc_throw(BadValue, "Free Lease Queue allocator is not supported for IPv6 address pools"); + + } else { + setAllocator(Lease::TYPE_NA, + boost::make_shared + (Lease::TYPE_NA, shared_from_this())); + setAllocationState(Lease::TYPE_NA, SubnetIterativeAllocationState::create(shared_from_this())); + setAllocationState(Lease::TYPE_TA, SubnetIterativeAllocationState::create(shared_from_this())); } + auto pd_allocator_type = getPdAllocatorType(); + if (pd_allocator_type.empty()) { + pd_allocator_type = getDefaultPdAllocatorType(); + } // Repeat the same for the delegated prefix allocator. - if (getPdAllocatorType() == "random") { + if (pd_allocator_type == "random") { setAllocator(Lease::TYPE_PD, boost::make_shared (Lease::TYPE_PD, shared_from_this())); setAllocationState(Lease::TYPE_PD, SubnetAllocationStatePtr()); - } else if (getPdAllocatorType() == "flq") { + } else if (pd_allocator_type == "flq") { setAllocator(Lease::TYPE_PD, boost::make_shared (Lease::TYPE_PD, shared_from_this())); setAllocationState(Lease::TYPE_PD, SubnetAllocationStatePtr()); + + } else { + setAllocator(Lease::TYPE_PD, + boost::make_shared + (Lease::TYPE_PD, shared_from_this())); + setAllocationState(Lease::TYPE_PD, SubnetIterativeAllocationState::create(shared_from_this())); } // Create allocation states for NA pools. for (auto pool : pools_) { - if (getAllocatorType() == "random") { + if (allocator_type == "random") { pool->setAllocationState(PoolRandomAllocationState::create(pool)); } else { pool->setAllocationState(PoolIterativeAllocationState::create(pool)); @@ -851,7 +876,7 @@ Subnet6::createAllocators() { } // Create allocation states for TA pools. for (auto pool : pools_ta_) { - if (getAllocatorType() == "random") { + if (allocator_type == "random") { pool->setAllocationState(PoolRandomAllocationState::create(pool)); } else { pool->setAllocationState(PoolIterativeAllocationState::create(pool)); @@ -859,9 +884,9 @@ Subnet6::createAllocators() { } // Create allocation states for PD pools. for (auto pool : pools_pd_) { - if (getPdAllocatorType() == "random") { + if (pd_allocator_type == "random") { pool->setAllocationState(PoolRandomAllocationState::create(pool)); - } else if (getPdAllocatorType() == "flq") { + } else if (pd_allocator_type == "flq") { pool->setAllocationState(PoolFreeLeaseQueueAllocationState::create(pool)); } else { pool->setAllocationState(PoolIterativeAllocationState::create(pool)); diff --git a/src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc b/src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc index 7a7b0d6797..8706767a0e 100644 --- a/src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc +++ b/src/lib/dhcpsrv/tests/cb_ctl_dhcp_unittest.cc @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -372,6 +373,7 @@ public: // 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->setAllocatorType("random"); subnet->setSharedNetworkName("one"); mgr.getPool()->createUpdateSubnet4(BackendSelector::UNSPEC(), ServerSelector::ALL(), subnet); @@ -611,6 +613,8 @@ public: (getTimestamp("dhcp4_subnet") > lb_modification_time)) { ASSERT_TRUE(found_subnet); EXPECT_TRUE(found_subnet->hasFetchGlobalsFn()); + EXPECT_TRUE(boost::dynamic_pointer_cast + (found_subnet->getAllocator(Lease::TYPE_V4))); } else { EXPECT_FALSE(found_subnet); @@ -1088,6 +1092,90 @@ TEST_F(CBControlDHCPv4Test, ipReservationsNonUniqueRefused) { EXPECT_TRUE(HostMgr::instance().getIPReservationsUnique()); } +// This test verifies that the allocator type is inherited from the global to +// shared network to subnet level. +TEST_F(CBControlDHCPv4Test, allocatorInheritance) { + remoteStoreTestConfiguration(); + + // Set non-default global allocator. + StampedValuePtr global_parameter = StampedValue::create("allocator", + Element::create("flq")); + auto& mgr = ConfigBackendDHCPv4Mgr::instance(); + ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter4(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + global_parameter)); + addCreateAuditEntry("dhcp4_global_parameter", 1); + addCreateAuditEntry("dhcp4_shared_network", 1); + addCreateAuditEntry("dhcp4_subnet", 2); + + // Merge the configuration including the allocator setting into the current + // configuration. + ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + getTimestamp(-5), + audit_entries_)); + + // Get the subnet that has no allocator specification. The global allocator should + // be used for this subnet. + auto srv_cfg = CfgMgr::instance().getCurrentCfg(); + auto subnet = srv_cfg->getCfgSubnets4()->getBySubnetId(SubnetID(2)); + ASSERT_TRUE(subnet); + auto allocator = subnet->getAllocator(Lease::TYPE_V4); + ASSERT_TRUE(allocator); + EXPECT_EQ("flq", allocator->getType()); + + // Update the shared network but use a different allocator type for it. + auto updated_network = boost::make_shared("one"); + updated_network->setId(1); + updated_network->setModificationTime(getTimestamp("dhcp4_shared_network")); + updated_network->setAllocatorType("random"); + ASSERT_NO_THROW(mgr.getPool()->createUpdateSharedNetwork4(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + updated_network)); + + + // Include our subnet in this shared network. + auto updated_subnet = boost::make_shared(IOAddress("192.0.4.0"), 26, 1, 2, 3, + SubnetID(2)); + updated_subnet->setSharedNetworkName("one"); + ASSERT_NO_THROW(mgr.getPool()->createUpdateSubnet4(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + updated_subnet)); + // Merge the updated configuration. + ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + getTimestamp(-5), + audit_entries_)); + + // Get the subnet again. Expect that the subnet uses the shared network-level allocator. + subnet = srv_cfg->getCfgSubnets4()->getBySubnetId(SubnetID(2)); + ASSERT_TRUE(subnet); + allocator = subnet->getAllocator(Lease::TYPE_V4); + ASSERT_TRUE(allocator); + EXPECT_EQ("random", allocator->getType()); + + // Override the allocator at the subnet level. + updated_subnet = boost::make_shared(IOAddress("192.0.4.0"), 26, 1, 2, 3, SubnetID(2)); + updated_subnet->setModificationTime(getTimestamp("dhcp4_subnet")); + updated_subnet->setAllocatorType("iterative"); + ASSERT_NO_THROW(mgr.getPool()->createUpdateSubnet4(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + updated_subnet)); + + addCreateAuditEntry("dhcp4_subnet", 2); + ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + getTimestamp(-5), + audit_entries_)); + + // Get the subnet again and expect the subnet-level allocator. + subnet = srv_cfg->getCfgSubnets4()->getBySubnetId(SubnetID(2)); + ASSERT_TRUE(subnet); + allocator = subnet->getAllocator(Lease::TYPE_V4); + ASSERT_TRUE(allocator); + EXPECT_EQ("iterative", allocator->getType()); +} + // ************************ V6 tests ********************* /// @brief Naked @c CBControlDHCPv6 class exposing protected methods. @@ -1212,6 +1300,8 @@ public: // Insert subnets into the database. Subnet6Ptr subnet(new Subnet6(IOAddress("2001:db8:1::"), 64, 1, 2, 3, 4, SubnetID(1))); + subnet->setAllocatorType("random"); + subnet->setPdAllocatorType("random"); subnet->setModificationTime(getTimestamp("dhcp6_subnet")); subnet->setSharedNetworkName("one"); mgr.getPool()->createUpdateSubnet6(BackendSelector::UNSPEC(), ServerSelector::ALL(), @@ -1435,6 +1525,10 @@ public: if (hasConfigElement("dhcp6_subnet") && (getTimestamp("dhcp6_subnet") > lb_modification_time)) { ASSERT_TRUE(found_subnet); + EXPECT_TRUE(boost::dynamic_pointer_cast + (found_subnet->getAllocator(Lease::TYPE_NA))); + EXPECT_TRUE(boost::dynamic_pointer_cast + (found_subnet->getAllocator(Lease::TYPE_PD))); EXPECT_TRUE(found_subnet->hasFetchGlobalsFn()); } else { @@ -1889,4 +1983,175 @@ TEST_F(CBControlDHCPv6Test, ipReservationsNonUniqueRefused) { EXPECT_TRUE(HostMgr::instance().getIPReservationsUnique()); } +// This test verifies that the allocator type is inherited from the global to +// shared network to subnet level. +TEST_F(CBControlDHCPv6Test, allocatorInheritance) { + remoteStoreTestConfiguration(); + + // Set non-default global allocator. + StampedValuePtr global_parameter = StampedValue::create("allocator", + Element::create("random")); + auto& mgr = ConfigBackendDHCPv6Mgr::instance(); + ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter6(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + global_parameter)); + addCreateAuditEntry("dhcp6_global_parameter", 1); + addCreateAuditEntry("dhcp6_shared_network", 1); + addCreateAuditEntry("dhcp6_subnet", 2); + + // Merge the configuration including the allocator setting into the current + // configuration. + ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + getTimestamp(-5), + audit_entries_)); + + // Get the subnet that has no allocator specification. The global allocator should + // be used for this subnet. + auto srv_cfg = CfgMgr::instance().getCurrentCfg(); + auto subnet = srv_cfg->getCfgSubnets6()->getBySubnetId(SubnetID(2)); + ASSERT_TRUE(subnet); + auto allocator = subnet->getAllocator(Lease::TYPE_NA); + ASSERT_TRUE(allocator); + EXPECT_EQ("random", allocator->getType()); + + // Update the shared network but use a different allocator type for it. + auto updated_network = boost::make_shared("one"); + updated_network->setId(1); + updated_network->setModificationTime(getTimestamp("dhcp6_shared_network")); + updated_network->setAllocatorType("iterative"); + ASSERT_NO_THROW(mgr.getPool()->createUpdateSharedNetwork6(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + updated_network)); + + // Include our subnet in this shared network. + auto updated_subnet = boost::make_shared(IOAddress("2001:db8:2::"), 26, 1, 2, 3, 4, + SubnetID(2)); + updated_subnet->setSharedNetworkName("one"); + ASSERT_NO_THROW(mgr.getPool()->createUpdateSubnet6(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + updated_subnet)); + // Merge the updated configuration. + addCreateAuditEntry("dhcp6_shared_network", 1); + ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + getTimestamp(-5), + audit_entries_)); + + // Get the subnet again. Expect that the subnet uses the shared network-level allocator. + subnet = srv_cfg->getCfgSubnets6()->getBySubnetId(SubnetID(2)); + ASSERT_TRUE(subnet); + allocator = subnet->getAllocator(Lease::TYPE_NA); + ASSERT_TRUE(allocator); + EXPECT_EQ("iterative", allocator->getType()); + + // Override the allocator at the subnet level. + updated_subnet = boost::make_shared(IOAddress("2001:db8:2::"), 26, 1, 2, 3, 4, + SubnetID(2)); + updated_subnet->setSharedNetworkName("one"); + updated_subnet->setModificationTime(getTimestamp("dhcp6_subnet")); + updated_subnet->setAllocatorType("random"); + ASSERT_NO_THROW(mgr.getPool()->createUpdateSubnet6(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + updated_subnet)); + + ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + getTimestamp(-5), + audit_entries_)); + + // Get the subnet again and expect the subnet-level allocator. + subnet = srv_cfg->getCfgSubnets6()->getBySubnetId(SubnetID(2)); + ASSERT_TRUE(subnet); + allocator = subnet->getAllocator(Lease::TYPE_NA); + ASSERT_TRUE(allocator); + EXPECT_EQ("random", allocator->getType()); +} + +// This test verifies that the prefix delegation allocator type is inherited from the +// global to shared network to subnet level. +TEST_F(CBControlDHCPv6Test, pdAllocatorInheritance) { + remoteStoreTestConfiguration(); + + // Set non-default global allocator. + StampedValuePtr global_parameter = StampedValue::create("pd-allocator", + Element::create("flq")); + auto& mgr = ConfigBackendDHCPv6Mgr::instance(); + ASSERT_NO_THROW(mgr.getPool()->createUpdateGlobalParameter6(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + global_parameter)); + addCreateAuditEntry("dhcp6_global_parameter", 1); + addCreateAuditEntry("dhcp6_shared_network", 1); + addCreateAuditEntry("dhcp6_subnet", 2); + + // Merge the configuration including the allocator setting into the current + // configuration. + ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + getTimestamp(-5), + audit_entries_)); + + // Get the subnet that has no allocator specification. The global allocator should + // be used for this subnet. + auto srv_cfg = CfgMgr::instance().getCurrentCfg(); + auto subnet = srv_cfg->getCfgSubnets6()->getBySubnetId(SubnetID(2)); + ASSERT_TRUE(subnet); + auto allocator = subnet->getAllocator(Lease::TYPE_PD); + ASSERT_TRUE(allocator); + EXPECT_EQ("flq", allocator->getType()); + + // Update the shared network but use a different allocator type for it. + auto updated_network = boost::make_shared("one"); + updated_network->setId(1); + updated_network->setModificationTime(getTimestamp("dhcp6_shared_network")); + updated_network->setPdAllocatorType("iterative"); + ASSERT_NO_THROW(mgr.getPool()->createUpdateSharedNetwork6(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + updated_network)); + + // Include our subnet in this shared network. + auto updated_subnet = boost::make_shared(IOAddress("2001:db8:2::"), 26, 1, 2, 3, 4, + SubnetID(2)); + updated_subnet->setSharedNetworkName("one"); + ASSERT_NO_THROW(mgr.getPool()->createUpdateSubnet6(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + updated_subnet)); + // Merge the updated configuration. + addCreateAuditEntry("dhcp6_shared_network", 1); + ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + getTimestamp(-5), + audit_entries_)); + + // Get the subnet again. Expect that the subnet uses the shared network-level allocator. + subnet = srv_cfg->getCfgSubnets6()->getBySubnetId(SubnetID(2)); + ASSERT_TRUE(subnet); + allocator = subnet->getAllocator(Lease::TYPE_PD); + ASSERT_TRUE(allocator); + EXPECT_EQ("iterative", allocator->getType()); + + // Override the allocator at the subnet level. + updated_subnet = boost::make_shared(IOAddress("2001:db8:2::"), 26, 1, 2, 3, 4, + SubnetID(2)); + updated_subnet->setSharedNetworkName("one"); + updated_subnet->setModificationTime(getTimestamp("dhcp6_subnet")); + updated_subnet->setPdAllocatorType("random"); + ASSERT_NO_THROW(mgr.getPool()->createUpdateSubnet6(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + updated_subnet)); + + ASSERT_NO_THROW(ctl_.databaseConfigApply(BackendSelector::UNSPEC(), + ServerSelector::ALL(), + getTimestamp(-5), + audit_entries_)); + + // Get the subnet again and expect the subnet-level allocator. + subnet = srv_cfg->getCfgSubnets6()->getBySubnetId(SubnetID(2)); + ASSERT_TRUE(subnet); + allocator = subnet->getAllocator(Lease::TYPE_PD); + ASSERT_TRUE(allocator); + EXPECT_EQ("random", allocator->getType()); +} + + } diff --git a/src/lib/dhcpsrv/tests/flq_allocator_unittest.cc b/src/lib/dhcpsrv/tests/flq_allocator_unittest.cc index 5e60cf81b2..33ae382901 100644 --- a/src/lib/dhcpsrv/tests/flq_allocator_unittest.cc +++ b/src/lib/dhcpsrv/tests/flq_allocator_unittest.cc @@ -41,6 +41,12 @@ public: } }; +// Test that the allocator returns the correct type. +TEST_F(FreeLeaseQueueAllocatorTest4, getType) { + FreeLeaseQueueAllocator alloc(Lease::TYPE_V4, subnet_); + EXPECT_EQ("flq", alloc.getType()); +} + // Test populating free DHCPv4 leases to the queue. TEST_F(FreeLeaseQueueAllocatorTest4, populateFreeAddressLeases) { FreeLeaseQueueAllocator alloc(Lease::TYPE_V4, subnet_); @@ -353,6 +359,12 @@ public: }; +// Test that the allocator returns the correct type. +TEST_F(FreeLeaseQueueAllocatorTest6, getType) { + FreeLeaseQueueAllocator alloc(Lease::TYPE_NA, subnet_); + EXPECT_EQ("flq", alloc.getType()); +} + // Test populating free DHCPv6 address leases to the queue. TEST_F(FreeLeaseQueueAllocatorTest6, populateFreeAddressLeases) { FreeLeaseQueueAllocator alloc(Lease::TYPE_NA, subnet_); diff --git a/src/lib/dhcpsrv/tests/iterative_allocator_unittest.cc b/src/lib/dhcpsrv/tests/iterative_allocator_unittest.cc index c58f4b7a98..3d28607e45 100644 --- a/src/lib/dhcpsrv/tests/iterative_allocator_unittest.cc +++ b/src/lib/dhcpsrv/tests/iterative_allocator_unittest.cc @@ -21,6 +21,12 @@ namespace test { using IterativeAllocatorTest4 = AllocEngine4Test; +// Test that the allocator returns the correct type. +TEST_F(IterativeAllocatorTest4, getType) { + IterativeAllocator alloc(Lease::TYPE_V4, subnet_); + EXPECT_EQ("iterative", alloc.getType()); +} + // This test verifies that the allocator picks addresses that belong to the // pool TEST_F(IterativeAllocatorTest4, basic) { @@ -111,6 +117,12 @@ TEST_F(IterativeAllocatorTest4, manyPools) { using IterativeAllocatorTest6 = AllocEngine6Test; +// Test that the allocator returns the correct type. +TEST_F(IterativeAllocatorTest6, getType) { + IterativeAllocator alloc(Lease::TYPE_NA, subnet_); + EXPECT_EQ("iterative", alloc.getType()); +} + // This test verifies that the allocator picks addresses that belong to the // pool TEST_F(IterativeAllocatorTest6, basic) { diff --git a/src/lib/dhcpsrv/tests/network_unittest.cc b/src/lib/dhcpsrv/tests/network_unittest.cc index 7cdfc52d7b..9185d036ee 100644 --- a/src/lib/dhcpsrv/tests/network_unittest.cc +++ b/src/lib/dhcpsrv/tests/network_unittest.cc @@ -361,6 +361,12 @@ TEST_F(NetworkTest, inheritanceSupport4) { &Network4::setAllocatorType, "iterative", "random"); } + { + SCOPED_TRACE("default-allocator-type"); + testNetworkInheritance(&Network::getDefaultAllocatorType, + &Network::setDefaultAllocatorType, + "random", "flq", false); + } { SCOPED_TRACE("offer-lifetime"); testNetworkInheritance(&Network4::getOfferLft, @@ -485,6 +491,18 @@ TEST_F(NetworkTest, inheritanceSupport6) { &Network6::setPdAllocatorType, "iterative", "random"); } + { + SCOPED_TRACE("default-allocator-type"); + testNetworkInheritance(&Network::getDefaultAllocatorType, + &Network::setDefaultAllocatorType, + "random", "iterative", false); + } + { + SCOPED_TRACE("default-pd-allocator-type"); + testNetworkInheritance(&Network6::getDefaultPdAllocatorType, + &Network6::setDefaultPdAllocatorType, + "random", "iterative", false); + } { SCOPED_TRACE("ddns-ttl-percent"); testNetworkInheritance(&Network::getDdnsTtlPercent, diff --git a/src/lib/dhcpsrv/tests/random_allocator_unittest.cc b/src/lib/dhcpsrv/tests/random_allocator_unittest.cc index e24e040e02..55fec78d2f 100644 --- a/src/lib/dhcpsrv/tests/random_allocator_unittest.cc +++ b/src/lib/dhcpsrv/tests/random_allocator_unittest.cc @@ -23,6 +23,12 @@ namespace test { using RandomAllocatorTest4 = AllocEngine4Test; +// Test that the allocator returns the correct type. +TEST_F(RandomAllocatorTest4, getType) { + RandomAllocator alloc(Lease::TYPE_V4, subnet_); + EXPECT_EQ("random", alloc.getType()); +} + // Test allocating IPv4 addresses when a subnet has a single pool. TEST_F(RandomAllocatorTest4, singlePool) { RandomAllocator alloc(Lease::TYPE_V4, subnet_); @@ -178,6 +184,12 @@ TEST_F(RandomAllocatorTest4, clientClasses) { using RandomAllocatorTest6 = AllocEngine6Test; +// Test that the allocator returns the correct type. +TEST_F(RandomAllocatorTest6, getType) { + RandomAllocator alloc(Lease::TYPE_NA, subnet_); + EXPECT_EQ("random", alloc.getType()); +} + // Test allocating IPv6 addresses when a subnet has a single pool. TEST_F(RandomAllocatorTest6, singlePool) { RandomAllocator alloc(Lease::TYPE_NA, subnet_);