From: Francis Dupont Date: Mon, 11 Aug 2025 15:10:03 +0000 (+0200) Subject: [#226] Added getMin*Lft* X-Git-Tag: Kea-3.1.1~23 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=233e393735bed7a326cc733016fb19e5c5e5c8be;p=thirdparty%2Fkea.git [#226] Added getMin*Lft* --- diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index 8cb6e39b60..60928006c4 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@ -1891,6 +1891,20 @@ AllocEngine::reuseExpiredLease(Lease6Ptr& expired, ClientContext6& ctx, return (expired); } +namespace { +void sanitizeLifetimes6(AllocEngine::ClientContext6& ctx, + uint32_t& preferred, uint32_t& valid) { + // If preferred isn't set or insane, calculate it as valid_lft * 0.625. + if (!preferred || preferred > valid) { + preferred = ((valid * 5)/8); + LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, + ALLOC_ENGINE_V6_CALCULATED_PREFERRED_LIFETIME) + .arg(ctx.query_->getLabel()) + .arg(preferred); + } +} +} // end of anonymous namespace. + void AllocEngine::getLifetimes6(ClientContext6& ctx, uint32_t& preferred, uint32_t& valid) { // If the triplets are specified in one of our classes use it. @@ -1950,14 +1964,70 @@ AllocEngine::getLifetimes6(ClientContext6& ctx, uint32_t& preferred, uint32_t& v } } - // If preferred isn't set or insane, calculate it as valid_lft * 0.625. - if (!preferred || preferred > valid) { - preferred = ((valid * 5)/8); - LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, - ALLOC_ENGINE_V6_CALCULATED_PREFERRED_LIFETIME) - .arg(ctx.query_->getLabel()) - .arg(preferred); + sanitizeLifetimes6(ctx, preferred, valid); +} + +void +AllocEngine::getMinLifetimes6(ClientContext6& ctx, uint32_t& preferred, + uint32_t& valid) { + // If the triplets are specified in one of our classes use it. + // We use the first one we find for each lifetime. + Triplet candidate_preferred; + Triplet candidate_valid; + const ClientClasses classes = ctx.query_->getClasses(); + if (!classes.empty()) { + // Let's get class definitions + const ClientClassDictionaryPtr& dict = + CfgMgr::instance().getCurrentCfg()->getClientClassDictionary(); + + // Iterate over the assigned class definitions. + int have_both = 0; + for (auto const& name : classes) { + ClientClassDefPtr cl = dict->findClass(name); + if (candidate_preferred.unspecified() && + (cl && (!cl->getPreferred().unspecified()))) { + candidate_preferred = cl->getPreferred(); + ++have_both; + } + + if (candidate_valid.unspecified() && + (cl && (!cl->getValid().unspecified()))) { + candidate_valid = cl->getValid(); + ++have_both; + } + if (have_both == 2) { + break; + } + } + } + + // If no classes specified preferred lifetime, get it from the subnet. + if (!candidate_preferred) { + candidate_preferred = ctx.subnet_->getPreferred(); + } + + // If no classes specified valid lifetime, get it from the subnet. + if (!candidate_valid) { + candidate_valid = ctx.subnet_->getValid(); + } + + // Save remaining values. + uint32_t remain_preferred(preferred); + uint32_t remain_valid(valid); + + // Set the outbound parameters to the minimal values. + preferred = candidate_preferred.getMin(); + valid = candidate_valid.getMin(); + + // Return at least the remaining values. + if (remain_preferred > preferred) { + preferred = remain_preferred; } + if (remain_valid > valid) { + valid = remain_valid; + } + + sanitizeLifetimes6(ctx, preferred, valid); } Lease6Ptr AllocEngine::createLease6(ClientContext6& ctx, @@ -4338,6 +4408,50 @@ AllocEngine::getValidLft(const ClientContext4& ctx) { return (candidate_lft.get()); } +void +AllocEngine::getMinValidLft(const ClientContext4& ctx, uint32_t& valid) { + // If it's BOOTP, use infinite valid lifetime. + if (ctx.query_->inClass("BOOTP")) { + valid = Lease::INFINITY_LFT; + return; + } + + // If the triplet is specified in one of our classes use it. + // We use the first one we find. + Triplet candidate_lft; + const ClientClasses classes = ctx.query_->getClasses(); + if (!classes.empty()) { + // Let's get class definitions + const ClientClassDictionaryPtr& dict = + CfgMgr::instance().getCurrentCfg()->getClientClassDictionary(); + + // Iterate over the assigned class definitions. + for (auto const& name : classes) { + ClientClassDefPtr cl = dict->findClass(name); + if (cl && (!cl->getValid().unspecified())) { + candidate_lft = cl->getValid(); + break; + } + } + } + + // If no classes specified it, get it from the subnet. + if (!candidate_lft) { + candidate_lft = ctx.subnet_->getValid(); + } + + // Save remaining value. + uint32_t remain(valid); + + // Set to the minimal value. + valid = candidate_lft.getMin(); + + // Return at least the remaining value. + if (remain > valid) { + valid = remain; + } +} + Lease4Ptr AllocEngine::createLease4(const ClientContext4& ctx, const IOAddress& addr, CalloutHandle::CalloutNextStep& callout_status) { diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h index 1e32036a5a..90fb16ee20 100644 --- a/src/lib/dhcpsrv/alloc_engine.h +++ b/src/lib/dhcpsrv/alloc_engine.h @@ -820,6 +820,22 @@ public: /// @param [out] valid set to the valid lifetime that should be used. static void getLifetimes6(ClientContext6& ctx, uint32_t& preferred, uint32_t& valid); + + /// @brief Determines the preferred and valid v6 lease lifetimes when + /// the pool occupancy is over the adaptive lease time threshold. + /// + /// As for the common case find the candidate triplet and return + /// minimal values. Requested lifetimes are ignored but remaining + /// lifetimes are returned when greater than minimal. + /// + /// @param ctx client context that passes all necessary information. See + /// @ref ClientContext6 for details. + /// @param [in/out] preferred set to the preferred lifetime that should + // be used. Caller must set it to 0 or remaining value. + /// @param [in/out] valid set to the valid lifetime that should be used. + /// Caller must set it to 0 or remaining value. + static void getMinLifetimes6(ClientContext6& ctx, uint32_t& preferred, + uint32_t& valid); private: /// @brief Creates a lease and inserts it in LeaseMgr if necessary @@ -1537,6 +1553,22 @@ public: /// @return unsigned integer value of the valid lifetime to use. static uint32_t getValidLft(const ClientContext4& ctx); + /// @brief Returns the valid lifetime based on the v4 context when + /// the pool occupancy is over the adaptive lease time threshold. + /// + /// If the client query is a BOOTP query, the value returned will + /// be Lease::INFINITY_LFT. + /// + /// Otherwise, as for the common case find the canndidate triplet + /// and return the minimal value. Requested lifetime is ignored but + /// remaining lifetime is returned when greater than minimal. + /// + /// @param ctx Client context holding various information about the client. + /// @param [in/out] valid set to the valid lifetime that should be used. + /// Caller must set it to 0 or remaining value. + /// @return unsigned integer value of the valid lifetime to use. + static void getMinValidLft(const ClientContext4& ctx, uint32_t& valid); + /// @brief Returns the offer lifetime based on the v4 context /// /// If the client query is a BOOTP query or something other than diff --git a/src/lib/dhcpsrv/cfg_hosts.h b/src/lib/dhcpsrv/cfg_hosts.h index 91a969bc34..80ea6f41a6 100644 --- a/src/lib/dhcpsrv/cfg_hosts.h +++ b/src/lib/dhcpsrv/cfg_hosts.h @@ -840,7 +840,7 @@ private: /// @return Collection of const @c Host objects. ConstHostCollection getAllInternal4(const SubnetID& subnet_id, - const asiolink::IOAddress& address) const; + const asiolink::IOAddress& address) const; /// @brief Returns @c Host objects for the specified (Subnet-id,IPv6 address) tuple. /// diff --git a/src/lib/dhcpsrv/cfg_option.h b/src/lib/dhcpsrv/cfg_option.h index 9c8ef0f75a..d5c2558523 100644 --- a/src/lib/dhcpsrv/cfg_option.h +++ b/src/lib/dhcpsrv/cfg_option.h @@ -904,7 +904,7 @@ public: /// @return A pointer to the unparsed configuration. isc::data::ElementPtr toElementWithMetadata(const bool include_metadata, - CfgOptionDefPtr cfg_option_def = CfgOptionDefPtr()) const; + CfgOptionDefPtr cfg_option_def = CfgOptionDefPtr()) const; private: diff --git a/src/lib/dhcpsrv/database_backends.dox b/src/lib/dhcpsrv/database_backends.dox index 620984c974..85dc9d85d4 100644 --- a/src/lib/dhcpsrv/database_backends.dox +++ b/src/lib/dhcpsrv/database_backends.dox @@ -155,7 +155,7 @@ example: 0 - fqdn_rev - FQDN reverse DNS RR update flag \n - type: bool (0 or 1) \n + type: bool (0 or 1) \n example: 1 - hostname - hostname \n diff --git a/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc index 560d6cff3b..56cf3dc8ae 100644 --- a/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc +++ b/src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc @@ -4850,52 +4850,6 @@ TEST_F(AllocEngine4Test, getValidLft4) { } } -// Verifies that AllocEngine::getRemaining retuns the remaining lifetime value. -TEST_F(AllocEngine4Test, getRemaining) { - // No Lease. - uint32_t valid(1); - Lease4Ptr lease; - AllocEngine::getRemaining(lease, valid); - EXPECT_EQ(0, valid); - - // Unexpected state. - valid = 1; - uint8_t hwaddr_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe }; - HWAddrPtr hwaddr(new HWAddr(hwaddr_data, sizeof(hwaddr_data), HTYPE_ETHER)); - uint8_t clientid[] = { 8, 7, 6, 5, 4, 3, 2, 1 }; - time_t now = time(0); - lease.reset(new Lease4(IOAddress("192.0.2.100"), hwaddr, clientid, - sizeof(clientid), 100, now, 1)); - lease->state_ = Lease::STATE_DECLINED; - AllocEngine::getRemaining(lease, valid); - EXPECT_EQ(0, valid); - - // Infinite lifetime. - lease->state_ = Lease::STATE_DEFAULT; - uint32_t infinity_lft = Lease::INFINITY_LFT; - lease->valid_lft_ = lease->current_valid_lft_ = infinity_lft; - AllocEngine::getRemaining(lease, valid); - EXPECT_EQ(infinity_lft, valid); - - // Time going backward. - lease->cltt_ = lease->current_cltt_ = now + 100; - lease->valid_lft_ = lease->current_valid_lft_ = 50; - AllocEngine::getRemaining(lease, valid); - EXPECT_EQ(0, valid); - - // Already expired. - valid = 1; - lease->cltt_ = lease->current_cltt_ = now - 100; - AllocEngine::getRemaining(lease, valid); - EXPECT_EQ(0, valid); - - // Valid case. - now = time(0); - lease->cltt_ = lease->current_cltt_ = now - 10; - AllocEngine::getRemaining(lease, valid); - EXPECT_NEAR(40, valid, 1); -} - // Verifies that AllocEngine::getValidLft(ctx4) returns the appropriate // lifetime value based on the context content. TEST_F(AllocEngine4Test, getTemplateClassValidLft4) { @@ -5048,6 +5002,209 @@ TEST_F(AllocEngine4Test, getTemplateClassValidLft4) { } } +// Verifies that AllocEngine::getMinValidLft(ctx4, valid) sets the appropriate +// lifetime value based on the context content. +TEST_F(AllocEngine4Test, getMinValidLft4) { + AllocEngine engine(0); + + // Let's make three classes, two with valid-lifetime and one without, + // and add them to the dictionary. + ClientClassDictionaryPtr dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary(); + ExpressionPtr match_expr; + ExpressionParser parser; + + ElementPtr test_cfg = Element::create("'valid_one_value'"); + parser.parse(match_expr, test_cfg, AF_INET, EvalContext::acceptAll, EvalContext::PARSER_STRING); + + ClientClassDefPtr class_def(new TemplateClientClassDef("valid_one", match_expr)); + Triplet valid_one(50, 100, 150); + class_def->setValid(valid_one); + dictionary->addClass(class_def); + + test_cfg = Element::create("'valid_two_value'"); + parser.parse(match_expr, test_cfg, AF_INET, EvalContext::acceptAll, EvalContext::PARSER_STRING); + + class_def.reset(new TemplateClientClassDef("valid_two", match_expr)); + Tripletvalid_two(200, 250, 300); + class_def->setValid(valid_two); + dictionary->addClass(class_def); + + test_cfg = Element::create("'valid_unspec_value'"); + parser.parse(match_expr, test_cfg, AF_INET, EvalContext::acceptAll, EvalContext::PARSER_STRING); + + class_def.reset(new TemplateClientClassDef("valid_unspec", match_expr)); + dictionary->addClass(class_def); + + // Commit our class changes. + CfgMgr::instance().commit(); + + // Update the subnet's triplet to something more useful. + subnet_->setValid(Triplet(500, 1000, 1500)); + + // Describes a test scenario. + struct Scenario { + std::string desc_; // descriptive text for logging + std::vector classes_; // class list of assigned classes + uint32_t requested_lft_; // use as option 51 is > 0 + uint32_t remaining_lft_; // remaining lifime or 0 + uint32_t exp_valid_; // expected lifetime + }; + + // Scenarios to test. + std::vector scenarios = { + { + "BOOTP", + { "BOOTP" }, + 0, + 0, + Lease::INFINITY_LFT + }, + { + "no classes, no option, remain 0", + {}, + 0, + 0, + subnet_->getValid().getMin() + }, + { + "no classes, no option, remain too small", + {}, + 0, + 100, + subnet_->getValid().getMin() + }, + { + "no classes, no option, remain", + {}, + 0, + 800, + 800 + }, + { + "no classes, option, remain 0", + {}, + 1000, + 0, + subnet_->getValid().getMin() + }, + { + "class unspecified, no option, remain 0", + { "valid_unspec" }, + 0, + 0, + subnet_->getValid().getMin() + }, + { + "from last class, no option, remain 0", + { "valid_unspec", "valid_one" }, + 0, + 0, + valid_one.getMin() + }, + { + "from first class, no option, remain 0", + { "valid_two", "valid_one" }, + 0, + 0, + valid_two.getMin() + }, + { + "class plus remain too small", + { "valid_one" }, + 0, + 10, + valid_one.getMin(), + }, + { + "class plus remain", + { "valid_one" }, + 0, + 100, + 100 + } + }; + + // Iterate over the scenarios and verify the correct outcome. + for (auto const& scenario : scenarios) { + SCOPED_TRACE(scenario.desc_); { + // Create a context; + AllocEngine::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_, + IOAddress("0.0.0.0"), false, false, + "", false); + ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); + + // Add client classes (if any) + for (auto const& class_name : scenario.classes_) { + if (class_name == "BOOTP") { + ctx.query_->addClass(class_name); + } else { + string subclass(TemplateClientClassDef::SPAWN_CLASS_PREFIX); + subclass += class_name; + subclass += "_value"; + ctx.query_->addSubClass(class_name, subclass); + } + } + + // Add client option (if one) + if (scenario.requested_lft_) { + OptionUint32Ptr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME, + scenario.requested_lft_)); + ctx.query_->addOption(opt); + } + + uint32_t valid = scenario.remaining_lft_; + engine.getMinValidLft(ctx, valid); + EXPECT_EQ(valid, scenario.exp_valid_); + } + } +} + +// Verifies that AllocEngine::getRemaining retuns the remaining lifetime value. +TEST_F(AllocEngine4Test, getRemaining) { + // No Lease. + uint32_t valid(1); + Lease4Ptr lease; + AllocEngine::getRemaining(lease, valid); + EXPECT_EQ(0, valid); + + // Unexpected state. + valid = 1; + uint8_t hwaddr_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe }; + HWAddrPtr hwaddr(new HWAddr(hwaddr_data, sizeof(hwaddr_data), HTYPE_ETHER)); + uint8_t clientid[] = { 8, 7, 6, 5, 4, 3, 2, 1 }; + time_t now = time(0); + lease.reset(new Lease4(IOAddress("192.0.2.100"), hwaddr, clientid, + sizeof(clientid), 100, now, 1)); + lease->state_ = Lease::STATE_DECLINED; + AllocEngine::getRemaining(lease, valid); + EXPECT_EQ(0, valid); + + // Infinite lifetime. + lease->state_ = Lease::STATE_DEFAULT; + uint32_t infinity_lft = Lease::INFINITY_LFT; + lease->valid_lft_ = lease->current_valid_lft_ = infinity_lft; + AllocEngine::getRemaining(lease, valid); + EXPECT_EQ(infinity_lft, valid); + + // Time going backward. + lease->cltt_ = lease->current_cltt_ = now + 100; + lease->valid_lft_ = lease->current_valid_lft_ = 50; + AllocEngine::getRemaining(lease, valid); + EXPECT_EQ(0, valid); + + // Already expired. + valid = 1; + lease->cltt_ = lease->current_cltt_ = now - 100; + AllocEngine::getRemaining(lease, valid); + EXPECT_EQ(0, valid); + + // Valid case. + now = time(0); + lease->cltt_ = lease->current_cltt_ = now - 10; + AllocEngine::getRemaining(lease, valid); + EXPECT_NEAR(40, valid, 1); +} + // This test checks that deleteRelease handles BOOTP leases. TEST_F(AllocEngine4Test, bootpDelete) { boost::scoped_ptr engine; diff --git a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc index 1bc3739c2c..08b36ba213 100644 --- a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc +++ b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc @@ -6027,62 +6027,6 @@ TEST_F(AllocEngine6Test, getTemplateClassValidLifetime) { } } -// Verifies that AllocEngine::getRemaining retuns the remaining lifetime values. -TEST_F(AllocEngine6Test, getRemaining) { - // No Lease. - uint32_t valid(1); - uint32_t preferred(1); - Lease6Ptr lease; - AllocEngine::getRemaining(lease, valid, preferred); - EXPECT_EQ(0, valid); - EXPECT_EQ(0, preferred); - - // Unexpected state. - valid = 1; - preferred = 1; - DuidPtr duid(new DUID(vector(12, 0xff))); - const uint32_t iaid = 3568; - time_t now = time(0); - lease.reset(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"), duid, - iaid, 30, 50, 1)); - lease->state_ = Lease::STATE_DECLINED; - AllocEngine::getRemaining(lease, valid, preferred); - EXPECT_EQ(0, valid); - EXPECT_EQ(0, preferred); - - // Time going backward. - valid = 1; - preferred = 1; - lease->state_ = Lease::STATE_DEFAULT; - lease->cltt_ = lease->current_cltt_ = now + 100; - lease->valid_lft_ = lease->current_valid_lft_ = 50; - AllocEngine::getRemaining(lease, valid, preferred); - EXPECT_EQ(0, valid); - EXPECT_EQ(0, preferred); - - // Already expired. - valid = 1; - preferred = 1; - lease->cltt_ = lease->current_cltt_ = now - 100; - AllocEngine::getRemaining(lease, valid, preferred); - EXPECT_EQ(0, valid); - EXPECT_EQ(0, preferred); - - // Valid case. - now = time(0); - lease->cltt_ = lease->current_cltt_ = now - 10; - AllocEngine::getRemaining(lease, valid, preferred); - EXPECT_NEAR(40, valid, 1); - EXPECT_NEAR(20, preferred, 1); - - // No longer preferred. - now = time(0); - lease->cltt_ = lease->current_cltt_ = now - 40; - AllocEngine::getRemaining(lease, valid, preferred); - EXPECT_NEAR(10, valid, 1); - EXPECT_EQ(0, preferred); -} - // Verifies that AllocEngine::getLifetimes6() returns the appropriate // preferred lifetime value based on the context content. TEST_F(AllocEngine6Test, getPreferredLifetime) { @@ -6380,6 +6324,358 @@ TEST_F(AllocEngine6Test, getTemplateClassPreferredLifetime) { } } +// Verifies that AllocEngine::getMinLifetimes6() returns the appropriate +// valid lifetime value based on the context content. +TEST_F(AllocEngine6Test, getMinValidLifetime) { + boost::scoped_ptr engine; + ASSERT_NO_THROW(engine.reset(new AllocEngine(100))); + ASSERT_TRUE(engine); + + // Let's make three classes, two with valid-lifetime and one without, + // and add them to the dictionary. + ClientClassDictionaryPtr dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary(); + ExpressionPtr match_expr; + ExpressionParser parser; + + ElementPtr test_cfg = Element::create("'valid_one_value'"); + parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING); + + ClientClassDefPtr class_def(new TemplateClientClassDef("valid_one", match_expr)); + Triplet valid_one(50, 100, 150); + class_def->setValid(valid_one); + dictionary->addClass(class_def); + + test_cfg = Element::create("'valid_two_value'"); + parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING); + + class_def.reset(new TemplateClientClassDef("valid_two", match_expr)); + Tripletvalid_two(200, 250, 300); + class_def->setValid(valid_two); + dictionary->addClass(class_def); + + test_cfg = Element::create("'valid_unspec_value'"); + parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING); + + class_def.reset(new TemplateClientClassDef("valid_unspec", match_expr)); + dictionary->addClass(class_def); + + // Commit our class changes. + CfgMgr::instance().commit(); + + // Update the subnet's triplet to something more useful. + subnet_->setValid(Triplet(500, 1000, 1500)); + + // Describes a test scenario. + struct Scenario { + std::string desc_; // descriptive text for logging + std::vector classes_; // class list of assigned classes + uint32_t requested_lft_; // use as option 51 is > 0 + uint32_t remaining_lft_; // remaining valid lifetime + uint32_t exp_valid_; // expected lifetime + }; + + // Scenarios to test. + std::vector scenarios = { + { + "no classes, no hint, remain 0", + {}, + 0, + 0, + subnet_->getValid().getMin() + }, + { + "no classes, no hint, remain too small", + {}, + 0, + 100, + subnet_->getValid().getMin() + }, + { + "no classes, no hint, remain", + {}, + 0, + 800, + 800 + }, + { + "no classes, hint, remain 0", + {}, + 800, + 0, + subnet_->getValid().getMin() + }, + { + "class unspecified, no hint, remain 0", + { "valid_unspec" }, + 0, + 0, + subnet_->getValid().getMin() + }, + { + "from last class, no hint, remain 0", + { "valid_unspec", "valid_one" }, + 0, + 0, + valid_one.getMin() + }, + { + "from first class, no hint, remain 0", + { "valid_two", "valid_one" }, + 0, + 0, + valid_two.getMin() + }, + { + "class plus remain too small", + { "valid_one" }, + 0, + 10, + valid_one.getMin() + }, + { + "class plus remain", + { "valid_one" }, + 0, + 100, + 100 + } + }; + + // Iterate over the scenarios and verify the correct outcome. + for (auto const& scenario : scenarios) { + SCOPED_TRACE(scenario.desc_); { + // Create a context; + AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", true, + Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234))); + // Add client classes (if any) + for (auto const& class_name : scenario.classes_) { + ctx.query_->addClass(class_name); + } + + // Add hint + ctx.currentIA().iaid_ = iaid_; + + // prefix, prefixlen, preferred, valid + ctx.currentIA().addHint(IOAddress("::"), 128, 0, scenario.requested_lft_); + uint32_t valid = scenario.remaining_lft_; + uint32_t preferred = 0; + + engine->getMinLifetimes6(ctx, preferred, valid); + EXPECT_EQ(valid, scenario.exp_valid_); + } + } +} + +// Verifies that AllocEngine::getMinLifetimes6() returns the appropriate +// preferred lifetime value based on the context content. +TEST_F(AllocEngine6Test, getMinPreferredLifetime) { + boost::scoped_ptr engine; + ASSERT_NO_THROW(engine.reset(new AllocEngine(100))); + ASSERT_TRUE(engine); + + // Let's make three classes, two with preferred-lifetime and one without, + // and add them to the dictionary. + ClientClassDictionaryPtr dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary(); + ExpressionPtr match_expr; + ExpressionParser parser; + + ElementPtr test_cfg = Element::create("'preferred_one_value'"); + parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING); + + ClientClassDefPtr class_def(new TemplateClientClassDef("preferred_one", match_expr)); + Triplet preferred_one(50, 100, 150); + class_def->setPreferred(preferred_one); + dictionary->addClass(class_def); + + test_cfg = Element::create("'preferred_two_value'"); + parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING); + + class_def.reset(new TemplateClientClassDef("preferred_two", match_expr)); + Tripletpreferred_two(200, 250, 300); + class_def->setPreferred(preferred_two); + dictionary->addClass(class_def); + + test_cfg = Element::create("'preferred_unspec_value'"); + parser.parse(match_expr, test_cfg, AF_INET6, EvalContext::acceptAll, EvalContext::PARSER_STRING); + + class_def.reset(new TemplateClientClassDef("preferred_unspec", match_expr)); + dictionary->addClass(class_def); + + // Commit our class changes. + CfgMgr::instance().commit(); + + // Update the subnet's triplet to something more useful. Note that + // valid is 400 for the subnet. + subnet_->setPreferred(Triplet(300, 350, 450)); + + // Describes a test scenario. + struct Scenario { + std::string desc_; // descriptive text for logging + std::vector classes_; // class list of assigned classes + uint32_t requested_lft_; // use as option 51 is > 0 + uint32_t remaining_lft_; // remaining preferred lifetime + uint32_t exp_preferred_; // expected lifetime + }; + + // Scenarios to test. + std::vector scenarios = { + { + "no classes, no hint, remain 0", + {}, + 0, + 0, + subnet_->getPreferred().getMin() + }, + { + "no classes, no hint, remain too small", + {}, + 0, + 100, + subnet_->getPreferred().getMin() + }, + { + "no classes, no hint, remain", + {}, + 0, + 350, + 350 + }, + { + "no classes, no hint, remain too big", + {}, + 0, + 500, + subnet_->getValid().getMin() * 5 / 8 + }, + { + "no classes, hint, remain 0", + {}, + 800, + 0, + subnet_->getPreferred().getMin() + }, + { + "class unspecified, no hint, remain 0", + { "preferred_unspec" }, + 0, + 0, + subnet_->getPreferred().getMin() + }, + { + "from last class, no hint, remain 0", + { "preferred_unspec", "preferred_one" }, + 0, + 0, + preferred_one.getMin() + }, + { + "from first class, no hint, remain 0", + { "preferred_two", "preferred_one" }, + 0, + 0, + preferred_two.getMin() + }, + { + "class plus remain too small", + { "preferred_one" }, + 0, + 10, + preferred_one.getMin() + }, + { + "class plus remain", + { "preferred_one" }, + 0, + 100, + 100 + } + }; + + // Iterate over the scenarios and verify the correct outcome. + for (auto const& scenario : scenarios) { + SCOPED_TRACE(scenario.desc_); { + // Create a context; + AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", true, + Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234))); + // Add client classes (if any) + for (auto const& class_name : scenario.classes_) { + string subclass(TemplateClientClassDef::SPAWN_CLASS_PREFIX); + subclass += class_name; + subclass += "_value"; + ctx.query_->addSubClass(class_name, subclass); + } + + // Add hint + ctx.currentIA().iaid_ = iaid_; + + // prefix, prefixlen, preferred, valid + ctx.currentIA().addHint(IOAddress("::"), 128, scenario.requested_lft_, 0); + + uint32_t valid = 0; + uint32_t preferred = scenario.remaining_lft_; + + engine->getMinLifetimes6(ctx, preferred, valid); + EXPECT_EQ(preferred, scenario.exp_preferred_); + } + } +} + +// Verifies that AllocEngine::getRemaining retuns the remaining lifetime values. +TEST_F(AllocEngine6Test, getRemaining) { + // No Lease. + uint32_t valid(1); + uint32_t preferred(1); + Lease6Ptr lease; + AllocEngine::getRemaining(lease, valid, preferred); + EXPECT_EQ(0, valid); + EXPECT_EQ(0, preferred); + + // Unexpected state. + valid = 1; + preferred = 1; + DuidPtr duid(new DUID(vector(12, 0xff))); + const uint32_t iaid = 3568; + time_t now = time(0); + lease.reset(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"), duid, + iaid, 30, 50, 1)); + lease->state_ = Lease::STATE_DECLINED; + AllocEngine::getRemaining(lease, valid, preferred); + EXPECT_EQ(0, valid); + EXPECT_EQ(0, preferred); + + // Time going backward. + valid = 1; + preferred = 1; + lease->state_ = Lease::STATE_DEFAULT; + lease->cltt_ = lease->current_cltt_ = now + 100; + lease->valid_lft_ = lease->current_valid_lft_ = 50; + AllocEngine::getRemaining(lease, valid, preferred); + EXPECT_EQ(0, valid); + EXPECT_EQ(0, preferred); + + // Already expired. + valid = 1; + preferred = 1; + lease->cltt_ = lease->current_cltt_ = now - 100; + AllocEngine::getRemaining(lease, valid, preferred); + EXPECT_EQ(0, valid); + EXPECT_EQ(0, preferred); + + // Valid case. + now = time(0); + lease->cltt_ = lease->current_cltt_ = now - 10; + AllocEngine::getRemaining(lease, valid, preferred); + EXPECT_NEAR(40, valid, 1); + EXPECT_NEAR(20, preferred, 1); + + // No longer preferred. + now = time(0); + lease->cltt_ = lease->current_cltt_ = now - 40; + AllocEngine::getRemaining(lease, valid, preferred); + EXPECT_NEAR(10, valid, 1); + EXPECT_EQ(0, preferred); +} + } // namespace test } // namespace dhcp } // namespace isc diff --git a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc index fc10b8653b..3eac5244a5 100644 --- a/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc +++ b/src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc @@ -78,157 +78,157 @@ public: // Verifies valid permutations of ddns-ttl-percent, ddns-ttl, // ddns-ttl-min, and ddns-ttl-max values for SubnetX. template - void validDdnsTtlParmatersSubnet(int family) { - struct Scenario { - size_t line_no_; - std::string json_; - double ddns_ttl_percent_; - uint32_t ddns_ttl_; - uint32_t ddns_ttl_min_; - uint32_t ddns_ttl_max_; - }; - - std::list scenarios = { - { - __LINE__, - R"^({ - "id": 1, - "ddns-ttl": 100 - })^", - 0.0, 100, 0, 0 - },{ - __LINE__, - R"^({ - "id": 1, - "ddns-ttl-percent": 5.0 - })^", - 5.0, 0, 0, 0 - },{ - __LINE__, - R"^({ - "id": 1, - "ddns-ttl-min": 25 - })^", - 0.0, 0, 25, 0 - },{ - __LINE__, - R"^({ - "id": 1, - "ddns-ttl-max": 150 - })^", - 0.0, 0, 0, 150 - },{ - __LINE__, - R"^({ - "id": 1, - "ddns-ttl-min": 25, - "ddns-ttl-max": 150 - })^", - 0.0, 0, 25, 150 - },{ - __LINE__, - R"^({ - "id": 1, "subnet": "192.0.2.0/24", - "ddns-ttl-percent": 5.0, - "ddns-ttl-min": 25, - "ddns-ttl-max": 150 - })^", - 5.0, 0, 25, 150 - }}; + void validDdnsTtlParmatersSubnet(int family) { + struct Scenario { + size_t line_no_; + std::string json_; + double ddns_ttl_percent_; + uint32_t ddns_ttl_; + uint32_t ddns_ttl_min_; + uint32_t ddns_ttl_max_; + }; + + std::list scenarios = { + { + __LINE__, + R"^({ + "id": 1, + "ddns-ttl": 100 + })^", + 0.0, 100, 0, 0 + },{ + __LINE__, + R"^({ + "id": 1, + "ddns-ttl-percent": 5.0 + })^", + 5.0, 0, 0, 0 + },{ + __LINE__, + R"^({ + "id": 1, + "ddns-ttl-min": 25 + })^", + 0.0, 0, 25, 0 + },{ + __LINE__, + R"^({ + "id": 1, + "ddns-ttl-max": 150 + })^", + 0.0, 0, 0, 150 + },{ + __LINE__, + R"^({ + "id": 1, + "ddns-ttl-min": 25, + "ddns-ttl-max": 150 + })^", + 0.0, 0, 25, 150 + },{ + __LINE__, + R"^({ + "id": 1, "subnet": "192.0.2.0/24", + "ddns-ttl-percent": 5.0, + "ddns-ttl-min": 25, + "ddns-ttl-max": 150 + })^", + 5.0, 0, 25, 150 + }}; ElementPtr subnet_elem = Element::create(family == AF_INET ? "192.0.2.0/24" : "2001:db8::/64"); - for (const auto& scenario : scenarios) { - std::stringstream oss; - oss << "scenario at " << scenario.line_no_; - SCOPED_TRACE(oss.str()); - - // Parse configuration specified above. - ElementPtr config_element; - ASSERT_NO_THROW_LOG(config_element = Element::fromJSON(scenario.json_)); + for (const auto& scenario : scenarios) { + std::stringstream oss; + oss << "scenario at " << scenario.line_no_; + SCOPED_TRACE(oss.str()); + + // Parse configuration specified above. + ElementPtr config_element; + ASSERT_NO_THROW_LOG(config_element = Element::fromJSON(scenario.json_)); config_element->set("subnet", subnet_elem); - ParserType parser(family); + ParserType parser(family); - NetworkPtrType subnet; + NetworkPtrType subnet; - ASSERT_NO_THROW_LOG(subnet = parser.parse(config_element)); - ASSERT_TRUE(subnet); + ASSERT_NO_THROW_LOG(subnet = parser.parse(config_element)); + ASSERT_TRUE(subnet); - EXPECT_EQ(subnet->getDdnsTtlPercent().unspecified(), (scenario.ddns_ttl_percent_ == 0.0)); - EXPECT_EQ(subnet->getDdnsTtlPercent(), scenario.ddns_ttl_percent_); + EXPECT_EQ(subnet->getDdnsTtlPercent().unspecified(), (scenario.ddns_ttl_percent_ == 0.0)); + EXPECT_EQ(subnet->getDdnsTtlPercent(), scenario.ddns_ttl_percent_); - EXPECT_EQ(subnet->getDdnsTtl().unspecified(), (scenario.ddns_ttl_ == 0)); - EXPECT_EQ(subnet->getDdnsTtl(), scenario.ddns_ttl_); + EXPECT_EQ(subnet->getDdnsTtl().unspecified(), (scenario.ddns_ttl_ == 0)); + EXPECT_EQ(subnet->getDdnsTtl(), scenario.ddns_ttl_); - EXPECT_EQ(subnet->getDdnsTtlMin().unspecified(), (scenario.ddns_ttl_min_ == 0)); - EXPECT_EQ(subnet->getDdnsTtlMin(), scenario.ddns_ttl_min_); + EXPECT_EQ(subnet->getDdnsTtlMin().unspecified(), (scenario.ddns_ttl_min_ == 0)); + EXPECT_EQ(subnet->getDdnsTtlMin(), scenario.ddns_ttl_min_); - EXPECT_EQ(subnet->getDdnsTtlMax().unspecified(), (scenario.ddns_ttl_max_ == 0)); - EXPECT_EQ(subnet->getDdnsTtlMax(), scenario.ddns_ttl_max_); - } - } + EXPECT_EQ(subnet->getDdnsTtlMax().unspecified(), (scenario.ddns_ttl_max_ == 0)); + EXPECT_EQ(subnet->getDdnsTtlMax(), scenario.ddns_ttl_max_); + } + } - // Verifies invalid permutations of ddns-ttl-percent, ddns-ttl, - // ddns-ttl-min, and ddns-ttl-max values for SubnetX. + // Verifies invalid permutations of ddns-ttl-percent, ddns-ttl, + // ddns-ttl-min, and ddns-ttl-max values for SubnetX. template - void invalidDdnsTtlParmatersSubnet(int family) { - struct Scenario { - size_t line_no_; - std::string json_; - std::string exp_message_; - }; - - std::list scenarios = { - { - __LINE__, - R"^({ - "id": 1, - "ddns-ttl-percent": 5.0, - "ddns-ttl": 100 - })^", - "subnet configuration failed: cannot specify both ddns-ttl-percent and ddns-ttl" - },{ - __LINE__, - R"^({ - "id": 1, - "ddns-ttl": 100, - "ddns-ttl-min": 25 - })^", - "subnet configuration failed: cannot specify both ddns-ttl-min and ddns-ttl" - },{ - __LINE__, - R"^({ - "id": 1, - "ddns-ttl": 100, - "ddns-ttl-max": 150 - })^", - "subnet configuration failed: cannot specify both ddns-ttl-max and ddns-ttl" - },{ - __LINE__, - R"^({ - "id": 1, - "ddns-ttl-min": 150, - "ddns-ttl-max": 25 - })^", - "subnet configuration failed: ddns-ttl-max: 25 must be greater than ddns-ttl-min: 150" - }}; + void invalidDdnsTtlParmatersSubnet(int family) { + struct Scenario { + size_t line_no_; + std::string json_; + std::string exp_message_; + }; + + std::list scenarios = { + { + __LINE__, + R"^({ + "id": 1, + "ddns-ttl-percent": 5.0, + "ddns-ttl": 100 + })^", + "subnet configuration failed: cannot specify both ddns-ttl-percent and ddns-ttl" + },{ + __LINE__, + R"^({ + "id": 1, + "ddns-ttl": 100, + "ddns-ttl-min": 25 + })^", + "subnet configuration failed: cannot specify both ddns-ttl-min and ddns-ttl" + },{ + __LINE__, + R"^({ + "id": 1, + "ddns-ttl": 100, + "ddns-ttl-max": 150 + })^", + "subnet configuration failed: cannot specify both ddns-ttl-max and ddns-ttl" + },{ + __LINE__, + R"^({ + "id": 1, + "ddns-ttl-min": 150, + "ddns-ttl-max": 25 + })^", + "subnet configuration failed: ddns-ttl-max: 25 must be greater than ddns-ttl-min: 150" + }}; ElementPtr subnet_elem = Element::create(family == AF_INET ? "192.0.2.0/24" : "2001:db8::/64"); - for (const auto& scenario : scenarios) { - std::stringstream oss; - oss << "scenario at " << scenario.line_no_; - SCOPED_TRACE(oss.str()); - - // Parse configuration specified above. - ElementPtr config_element; - ASSERT_NO_THROW_LOG(config_element = Element::fromJSON(scenario.json_)); + for (const auto& scenario : scenarios) { + std::stringstream oss; + oss << "scenario at " << scenario.line_no_; + SCOPED_TRACE(oss.str()); + + // Parse configuration specified above. + ElementPtr config_element; + ASSERT_NO_THROW_LOG(config_element = Element::fromJSON(scenario.json_)); config_element->set("subnet", subnet_elem); - ParserType parser(family); - ASSERT_THROW_MSG(parser.parse(config_element), DhcpConfigError, scenario.exp_message_); - } - } + ParserType parser(family); + ASSERT_THROW_MSG(parser.parse(config_element), DhcpConfigError, scenario.exp_message_); + } + } /// @brief Tests valid DDNS parameters in v4 or v6 pools. /// @@ -241,7 +241,7 @@ public: /// @param pool1 string pool specification for the first pool /// @param pool2 string pool specification for the first pool template - void validPoolDdnsParameters(const std::string& pool1, const std::string& pool2) { + void validPoolDdnsParameters(const std::string& pool1, const std::string& pool2) { std::stringstream ss; ss << @@ -456,8 +456,8 @@ public: /// @brief Sets the Hooks path from which hooks can be loaded. /// @param custom_path path to use as the hooks path. void setHooksTestPath(const std::string explicit_path = "") { - HooksLibrariesParser::getHooksPath(true, - (!explicit_path.empty() ? + HooksLibrariesParser::getHooksPath(true, + (!explicit_path.empty() ? explicit_path : DHCPSRV_HOOKS_TEST_PATH)); } @@ -4472,12 +4472,12 @@ TEST_F(DhcpParserTest, invalidDdnsTtlParmatersSubnet6) { // Verifies valid DDNS parameters in v4 pools. TEST_F(DhcpParserTest, validDdnsParmatersPool4) { - validPoolDdnsParameters("192.0.1.0/24", "192.0.2.0/24"); + validPoolDdnsParameters("192.0.1.0/24", "192.0.2.0/24"); } // Verifies valid DDNS parameters in v6 pools. TEST_F(DhcpParserTest, validDdnsParmatersPool6) { - validPoolDdnsParameters("2001:db8:1::/64", "2001:db8:2::/64"); + validPoolDdnsParameters("2001:db8:1::/64", "2001:db8:2::/64"); } } // Anonymous namespace