]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#226] Added getMin*Lft*
authorFrancis Dupont <fdupont@isc.org>
Mon, 11 Aug 2025 15:10:03 +0000 (17:10 +0200)
committerFrancis Dupont <fdupont@isc.org>
Wed, 20 Aug 2025 15:39:15 +0000 (17:39 +0200)
src/lib/dhcpsrv/alloc_engine.cc
src/lib/dhcpsrv/alloc_engine.h
src/lib/dhcpsrv/cfg_hosts.h
src/lib/dhcpsrv/cfg_option.h
src/lib/dhcpsrv/database_backends.dox
src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc
src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

index 8cb6e39b600d18a62e2cd22e45e5be023806c7ff..60928006c46cd966828af978ed1cc5b90ce13e47 100644 (file)
@@ -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<uint32_t> candidate_preferred;
+    Triplet<uint32_t> 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<uint32_t> 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) {
index 1e32036a5a2239a43577aa3ccf4f9694e6082919..90fb16ee20961ec45be42475d7ab31386e5570bf 100644 (file)
@@ -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
index 91a969bc344b9f6d008cb7c921007925b2e74c3e..80ea6f41a6eefe88709afb0c8820588aca16530f 100644 (file)
@@ -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.
     ///
index 9c8ef0f75a6c69bf6954fe226a9394cca5664e92..d5c25585238f6b77d9c7bb2df05092c23a96ac56 100644 (file)
@@ -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:
 
index 620984c974b81ae0e3afc82d9e690a7617690ac7..85dc9d85d468fcad1d582d9e7a0a5128faa756d9 100644 (file)
     example: 0
 
   - <b>fqdn_rev</b> - FQDN reverse DNS RR update flag \n
-    type: bool (0 or 1)        \n
+    type: bool (0 or 1) \n
     example: 1
 
   - <b>hostname</b> - hostname \n
index 560d6cff3baa4667ad4885f596d1c23172bae087..56cf3dc8aedd44fc567e9236783f5d5102d7b2d3 100644 (file)
@@ -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<uint32_t> 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));
+    Triplet<uint32_t>valid_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<uint32_t>(500, 1000, 1500));
+
+    // Describes a test scenario.
+    struct Scenario {
+        std::string desc_;                  // descriptive text for logging
+        std::vector<std::string> 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<Scenario> 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<AllocEngine> engine;
index 1bc3739c2c800a7f1237d7c17c12317e5753663a..08b36ba213ecba49662d5eeba8200ebbf231271b 100644 (file)
@@ -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<uint8_t>(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<AllocEngine> 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<uint32_t> 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));
+    Triplet<uint32_t>valid_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<uint32_t>(500, 1000, 1500));
+
+    // Describes a test scenario.
+    struct Scenario {
+        std::string desc_;                  // descriptive text for logging
+        std::vector<std::string> 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<Scenario> 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<AllocEngine> 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<uint32_t> 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));
+    Triplet<uint32_t>preferred_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<uint32_t>(300, 350, 450));
+
+    // Describes a test scenario.
+    struct Scenario {
+        std::string desc_;                  // descriptive text for logging
+        std::vector<std::string> 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<Scenario> 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<uint8_t>(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
index fc10b8653bc1a18d9468028d7219c82c9e221388..3eac5244a5f595dddeeaa9f415fb49363aa469f0 100644 (file)
@@ -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<typename ParserType, typename NetworkPtrType>
-       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<Scenario> 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<Scenario> 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<typename ParserType>
-       void invalidDdnsTtlParmatersSubnet(int family) {
-           struct Scenario {
-               size_t line_no_;
-               std::string json_;
-               std::string exp_message_;
-           };
-
-           std::list<Scenario> 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<Scenario> 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<typename PoolListParserType>
-       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<Pools4ListParser>("192.0.1.0/24", "192.0.2.0/24");
+        validPoolDdnsParameters<Pools4ListParser>("192.0.1.0/24", "192.0.2.0/24");
 }
 
 // Verifies valid DDNS parameters in v6 pools.
 TEST_F(DhcpParserTest, validDdnsParmatersPool6) {
-       validPoolDdnsParameters<Pools6ListParser>("2001:db8:1::/64", "2001:db8:2::/64");
+        validPoolDdnsParameters<Pools6ListParser>("2001:db8:1::/64", "2001:db8:2::/64");
 }
 
 }  // Anonymous namespace