]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1418] Updated v6 tests and doc
authorFrancis Dupont <fdupont@isc.org>
Fri, 13 Nov 2020 12:03:48 +0000 (13:03 +0100)
committerFrancis Dupont <fdupont@isc.org>
Mon, 11 Jan 2021 15:05:46 +0000 (16:05 +0100)
doc/sphinx/arm/dhcp4-srv.rst
doc/sphinx/arm/dhcp6-srv.rst
src/bin/dhcp4/tests/dhcp4_srv_unittest.cc
src/bin/dhcp4/tests/hooks_unittest.cc
src/bin/dhcp6/dhcp6_srv.cc
src/bin/dhcp6/tests/dhcp6_srv_unittest.cc
src/bin/dhcp6/tests/hooks_unittest.cc
src/lib/dhcpsrv/libdhcpsrv.dox
src/lib/dhcpsrv/tests/alloc_engine4_unittest.cc
src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc

index 214c44bbc9812307d2fbaf498ce2daa17bb9047a..a2f021a0b5ec79c6fe3c166e5045d65efc851f0c 100644 (file)
@@ -3994,6 +3994,63 @@ enable the option for the whole subnet, the following configuration can be used:
         }
     ],
 
+Lease Caching
+-------------
+
+Clients that attempt renewal frequently can cause the server to update
+and write the database frequently resulting in a performance impact on
+the server. The cache parameters instructs the DHCP server to avoid
+updating leases too frequently thus avoiding this behavior. Instead
+the server assigns the same lease (i.e. reuses it) with no
+modifications except for CLTT (Client Last Transmission Time)
+which does not require disk operations.
+
+The two parameters are the ``cache-threshold`` double and the
+``cache-max-age`` integer and have no default, i.e. the lease caching
+feature must be explicitly enabled. These parameters can be configured
+at the global, shared network and subnet levels. The subnet level has
+the precedence on the shared network level, the global level is used
+as last resort. For example:
+
+::
+
+    "subnet4": [
+        {
+            "pools": [ { "pool":  "192.0.2.1 - 192.0.2.200" } ],
+            "subnet": "192.0.2.0/24",
+            "cache-threshold": .25,
+           "cache-max-age": 600,
+            "valid-lifetime": 2000,
+            ...
+        }
+    ],
+
+When an already assigned lease can fulfill a client query:
+
+  - any important change e.g. for DDNS parameter, hostname or
+    valid lifetime reduction makes the lease not reusable
+
+  - lease age i.e. the difference between the creation or last modification
+    time and the current time is computed (elapsed duration)
+
+  - if the ``cache-max-age`` is configured, it is compared with the age
+    and too old leases are not reusable (this means that the value 0
+    for ``cache-max-age`` disables the lease cache feature)
+
+  - if the ``cache-threshold`` is configured and is between 0. and 1.
+    it expresses the percentage of the lease valid lifetime which is
+    allowed for the lease age. Values below and including 0. and
+    values greater than 1. disables the lease cache feature.
+    
+In the example a lease with a valid lifetime of 2000 seconds can be
+reused if it was committed less than 500 seconds ago. With a lifetime
+of 3000 seconds the maximum age of 600 seconds applies.
+
+In responses the ``dhcp-lease-time`` option is set to the remaining
+valid lifetime i.e. the expiration date does not change. Other options
+based on the valid lifetime e.g. ``dhcp-renewal-time`` and
+``dhcp-rebinding-time`` also use the remaining lifetime.
+
 .. _host-reservation-v4:
 
 Host Reservation in DHCPv4
@@ -6749,6 +6806,10 @@ used by all servers connecting to the configuration database.
    +-----------------------------+----------------------------+-------------+-------------+-------------+
    | boot-file-name              | yes                        | yes         | yes         | n/a         |
    +-----------------------------+----------------------------+-------------+-------------+-------------+
+   | cache-max-age               | yes                        | todo        | todo        | n/a         |
+   +-----------------------------+----------------------------+-------------+-------------+-------------+
+   | cache-threshold             | yes                        | todo        | todo        | n/a         |
+   +-----------------------------+----------------------------+-------------+-------------+-------------+
    | calculate-tee-times         | yes                        | yes         | yes         | n/a         |
    +-----------------------------+----------------------------+-------------+-------------+-------------+
    | client-class                | n/a                        | yes         | yes         | yes         |
index efdb8310d7efdda95c36b6c4467d99dcbebf2751..1268a63f5719de7d44a5bc85fb44da34d3da8d70 100644 (file)
@@ -3467,6 +3467,61 @@ Our tests reported best results when:
    storing leases. In our case it's 11 * 6 = 66.
 
 
+Lease Caching
+-------------
+
+Clients that attempt renewal frequently can cause the server to update
+and write the database frequently resulting in a performance impact on
+the server. The cache parameters instructs the DHCP server to avoid
+updating leases too frequently thus avoiding this behavior. Instead
+the server assigns the same lease (i.e. reuses it) with no
+modifications except for CLTT (Client Last Transmission Time)
+which does not require disk operations.
+
+The two parameters are the ``cache-threshold`` double and the
+``cache-max-age`` integer and have no default, i.e. the lease caching
+feature must be explicitly enabled. These parameters can be configured
+at the global, shared network and subnet levels. The subnet level has
+the precedence on the shared network level, the global level is used
+as last resort. For example:
+
+::
+
+    "subnet6": [
+        {
+            "subnet": "2001:db8:1:1::/64",
+            "pools": [ { "pool": "2001:db8:1:1::1:0/112" } ],
+            "cache-threshold": .25,
+           "cache-max-age": 600,
+            "valid-lifetime": 2000,
+            ...
+        }
+    ],
+
+When an already assigned lease can fulfill a client query:
+
+  - any important change e.g. for DDNS parameter, hostname, or
+    preferred or valid lifetime reduction makes the lease not reusable
+
+  - lease age i.e. the difference between the creation or last modification
+    time and the current time is computed (elapsed duration)
+
+  - if the ``cache-max-age`` is configured, it is compared with the age
+    and too old leases are not reusable (this means that the value 0
+    for ``cache-max-age`` disables the lease cache feature)
+
+  - if the ``cache-threshold`` is configured and is between 0. and 1.
+    it expresses the percentage of the lease valid lifetime which is
+    allowed for the lease age. Values below and including 0. and
+    values greater than 1. disables the lease cache feature.
+    
+In the example a lease with a valid lifetime of 2000 seconds can be
+reused if it was committed less than 500 seconds ago. With a lifetime
+of 3000 seconds the maximum age of 600 seconds applies.
+
+In responses used preferred and valid lifetimes are the remaining
+values i.e. the expiration dates do not change.
+
 .. _host-reservation-v6:
 
 Host Reservation in DHCPv6
@@ -6731,6 +6786,10 @@ the global DHCPv6 options (``option-data``) are modified using
    |                             |                            | Network   |           |           | Delegation |
    |                             |                            |           |           |           | Pool       |
    +=============================+============================+===========+===========+===========+============+
+   | cache-max-age               | yes                        | todo      | todo      | n/a       | n/a        |
+   +-----------------------------+----------------------------+-----------+-----------+-----------+------------+
+   | cache-threshold             | yes                        | todo      | todo      | n/a       | n/a        |
+   +-----------------------------+----------------------------+-----------+-----------+-----------+------------+
    | calculate-tee-times         | yes                        | yes       | yes       | n/a       | n/a        |
    +-----------------------------+----------------------------+-----------+-----------+-----------+------------+
    | client-class                | n/a                        | yes       | yes       | yes       | yes        |
index 586ead2042b9d1938f3ed03e45454c71dbd02dff..e8cb29565b4311da4c1bd1628890a60e42dfcc97 100644 (file)
@@ -1766,7 +1766,7 @@ TEST_F(Dhcpv4SrvTest, DiscoverCache) {
     ASSERT_TRUE(l);
 
     // Check that preferred, valid and cltt really set.
-    // Constructed lease looks as if it was assigned 10 seconds ago
+    // Constructed lease looks as if it was assigned 100 seconds ago
     EXPECT_EQ(l->valid_lft_, temp_valid);
     EXPECT_EQ(l->cltt_, temp_timestamp);
 
@@ -2273,7 +2273,7 @@ TEST_F(Dhcpv4SrvTest, RenewCache) {
     ASSERT_TRUE(l);
 
     // Check that preferred, valid and cltt really set.
-    // Constructed lease looks as if it was assigned 10 seconds ago
+    // Constructed lease looks as if it was assigned 100 seconds ago
     EXPECT_EQ(l->valid_lft_, temp_valid);
     EXPECT_EQ(l->cltt_, temp_timestamp);
 
index 3ac2411840e41294ef66eec620618c763530f943..3f1141b5e46808cdba21e191db5e648e96decc06 100644 (file)
@@ -2040,7 +2040,7 @@ TEST_F(HooksDhcpv4SrvTest, leases4CommittedRequest) {
 }
 
 // This test verifies that the callout installed on the leases4_committed hook
-// point is executed as a result of DHCPREQUEST message sent to reuse an
+// point is executed as a result of DHCPREQUEST message sent to reuse
 // an existing lease.
 TEST_F(HooksDhcpv4SrvTest, leases4CommittedCache) {
     IfaceMgrTestConfig test_config(true);
@@ -2098,7 +2098,7 @@ TEST_F(HooksDhcpv4SrvTest, leases4CommittedCache) {
     // Check that the callback called is indeed the one we installed
     EXPECT_EQ("leases4_committed", callback_name_);
 
-    // Renewed lease should not be present because it was renewed.
+    // Renewed lease should not be present because it was reused.
     EXPECT_FALSE(callback_lease4_);
 
     // Deleted lease must not be present, because it renews the same address.
index 5ddfe3f6a21a85bac6c0aa65198f6474c9a9c74c..13d4040f60b502784dd815e38571c60a851fafd0 100644 (file)
@@ -2344,11 +2344,7 @@ Dhcpv6Srv::extendIA_NA(const Pkt6Ptr& query, const Pkt6Ptr& answer,
     uint32_t min_preferred_lft = std::numeric_limits<uint32_t>::max();
 
     // For all leases we have now, add the IAADDR with non-zero lifetimes.
-    for (Lease6Collection::const_iterator l = leases.begin(); l != leases.end(); ++l) {
-        Option6IAAddrPtr iaaddr(new Option6IAAddr(D6O_IAADDR,
-                                (*l)->addr_, (*l)->preferred_lft_, (*l)->valid_lft_));
-        ia_rsp->addOption(iaaddr);
-
+    for (Lease6Collection::iterator l = leases.begin(); l != leases.end(); ++l) {
         if ((*l)->remaining_valid_lft_ == 0) {
             LOG_INFO(lease6_logger, DHCP6_LEASE_RENEW)
                 .arg(query->getLabel())
@@ -2364,6 +2360,10 @@ Dhcpv6Srv::extendIA_NA(const Pkt6Ptr& query, const Pkt6Ptr& answer,
                 .arg(Lease::lifetimeToText((*l)->valid_lft_));
         }
 
+        Option6IAAddrPtr iaaddr(new Option6IAAddr(D6O_IAADDR,
+                                (*l)->addr_, (*l)->preferred_lft_, (*l)->valid_lft_));
+        ia_rsp->addOption(iaaddr);
+
         // Check for new minimum lease time
         if (((*l)->preferred_lft_ > 0) && (min_preferred_lft > (*l)->preferred_lft_)) {
             min_preferred_lft = (*l)->preferred_lft_;
@@ -2376,8 +2376,8 @@ Dhcpv6Srv::extendIA_NA(const Pkt6Ptr& query, const Pkt6Ptr& answer,
     }
 
     // For the leases that we just retired, send the addresses with 0 lifetimes.
-    for (Lease6Collection::const_iterator l = ctx.currentIA().old_leases_.begin();
-                                          l != ctx.currentIA().old_leases_.end(); ++l) {
+    for (Lease6Collection::iterator l = ctx.currentIA().old_leases_.begin();
+         l != ctx.currentIA().old_leases_.end(); ++l) {
 
         // Send an address with zero lifetimes only when this lease belonged to
         // this client. Do not send it when we're reusing an old lease that belonged
@@ -2532,7 +2532,23 @@ Dhcpv6Srv::extendIA_PD(const Pkt6Ptr& query,
     // for calculating T1 and T2.
     uint32_t min_preferred_lft = std::numeric_limits<uint32_t>::max();
 
-    for (Lease6Collection::const_iterator l = leases.begin(); l != leases.end(); ++l) {
+    for (Lease6Collection::iterator l = leases.begin(); l != leases.end(); ++l) {
+        if ((*l)->remaining_valid_lft_ == 0) {
+            LOG_INFO(lease6_logger, DHCP6_PD_LEASE_RENEW)
+                .arg(query->getLabel())
+                .arg((*l)->addr_.toText())
+                .arg(static_cast<int>((*l)->prefixlen_))
+                .arg(ia->getIAID());
+        } else {
+            (*l)->valid_lft_ = (*l)->remaining_valid_lft_;
+            (*l)->preferred_lft_ = (*l)->remaining_preferred_lft_;
+            LOG_INFO(lease6_logger, DHCP6_PD_LEASE_REUSE)
+                .arg(query->getLabel())
+                .arg((*l)->addr_.toText())
+                .arg(static_cast<int>((*l)->prefixlen_))
+                .arg(ia->getIAID())
+                .arg(Lease::lifetimeToText((*l)->valid_lft_));
+        }
 
         Option6IAPrefixPtr prf(new Option6IAPrefix(D6O_IAPREFIX,
                                (*l)->addr_, (*l)->prefixlen_,
@@ -2553,23 +2569,6 @@ Dhcpv6Srv::extendIA_PD(const Pkt6Ptr& query,
             }
         }
 
-        if ((*l)->remaining_valid_lft_ == 0) {
-            LOG_INFO(lease6_logger, DHCP6_PD_LEASE_RENEW)
-                .arg(query->getLabel())
-                .arg((*l)->addr_.toText())
-                .arg(static_cast<int>((*l)->prefixlen_))
-                .arg(ia->getIAID());
-        } else {
-            (*l)->valid_lft_ = (*l)->remaining_valid_lft_;
-            (*l)->preferred_lft_ = (*l)->remaining_preferred_lft_;
-            LOG_INFO(lease6_logger, DHCP6_PD_LEASE_REUSE)
-                .arg(query->getLabel())
-                .arg((*l)->addr_.toText())
-                .arg(static_cast<int>((*l)->prefixlen_))
-                .arg(ia->getIAID())
-                .arg(Lease::lifetimeToText((*l)->valid_lft_));
-        }
-
         // Check for new minimum lease time
         if (((*l)->preferred_lft_ > 0) && ((*l)->preferred_lft_ < min_preferred_lft)) {
             min_preferred_lft = (*l)->preferred_lft_;
@@ -2582,8 +2581,8 @@ Dhcpv6Srv::extendIA_PD(const Pkt6Ptr& query,
     }
 
     /// For the leases that we just retired, send the prefixes with 0 lifetimes.
-    for (Lease6Collection::const_iterator l = ctx.currentIA().old_leases_.begin();
-                                          l != ctx.currentIA().old_leases_.end(); ++l) {
+    for (Lease6Collection::iterator l = ctx.currentIA().old_leases_.begin();
+         l != ctx.currentIA().old_leases_.end(); ++l) {
 
         // Send a prefix with zero lifetimes only when this lease belonged to
         // this client. Do not send it when we're reusing an old lease that belonged
index e516279cc07cabfdf3ab95f01e9da3a5e832d135..5284cfcf9340acf73d56e9731045d710a6399526 100644 (file)
@@ -997,6 +997,147 @@ TEST_F(Dhcpv6SrvTest, ManySolicits) {
     cout << "Offered address to client3=" << addr3->getAddress() << endl;
 }
 
+// This test verifies that incoming SOLICIT can't reuse an existing lease
+// and simply return it, i.e. fake allocation ignores the cache feature.
+TEST_F(Dhcpv6SrvTest, SolicitCache) {
+    NakedDhcpv6Srv srv(0);
+
+    // Enable lease reuse.
+    subnet_->setCacheThreshold(.1);
+
+    const IOAddress addr("2001:db8:1:1::cafe:babe");
+    const uint32_t iaid = 234;
+    const uint32_t pref = subnet_->getPreferred();
+    const uint32_t valid = subnet_->getValid();
+    const int delta = 100;
+    const time_t timestamp = time(NULL) - delta;
+
+    // Generate client-id also duid_.
+    OptionPtr clientid = generateClientId();
+
+    // Check that the address we are about to use is indeed in pool.
+    ASSERT_TRUE(subnet_->inPool(Lease::TYPE_NA, addr));
+
+    Lease6Ptr used(new Lease6(Lease::TYPE_NA, addr, duid_, iaid, pref, valid,
+                              subnet_->getID()));
+    used->cltt_ = timestamp;
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+    // Check that the lease is really in the database.
+    Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+    ASSERT_TRUE(l);
+
+    // Check that preferred, valid and cltt really set.
+    // Constructed lease looks as if it was assigned 100 seconds ago.
+    EXPECT_EQ(l->preferred_lft_, pref);
+    EXPECT_EQ(l->valid_lft_, valid);
+    EXPECT_EQ(l->cltt_, timestamp);
+
+    // Let's create a SOLICIT.
+    Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+    sol->setRemoteAddr(IOAddress("fe80::abcd"));
+    sol->setIface("eth0");
+    sol->setIndex(ETH0_INDEX);
+    sol->addOption(generateIA(D6O_IA_NA, 234, 1500, 3000));
+    sol->addOption(clientid);
+
+    // Pass it to the server and get an advertise
+    AllocEngine::ClientContext6 ctx;
+    bool drop = false;
+    srv.initContext(sol, ctx, drop);
+    ASSERT_FALSE(drop);
+    Pkt6Ptr reply = srv.processSolicit(ctx);
+
+    // check if we get response at all
+    checkResponse(reply, DHCPV6_ADVERTISE, 1234);
+
+    // check that IA_NA was returned and that there's an address included
+    boost::shared_ptr<Option6IAAddr> iaaddr =
+        checkIA_NA(reply, 234, subnet_->getT1(), subnet_->getT2());
+    ASSERT_TRUE(iaaddr);
+
+    // Check the address.
+    EXPECT_EQ(addr, iaaddr->getAddress());
+    EXPECT_EQ(pref, iaaddr->getPreferred());
+    EXPECT_EQ(valid, iaaddr->getValid());
+
+    // check DUIDs
+    checkServerId(reply, srv.getServerID());
+    checkClientId(reply, clientid);
+}
+
+// This test verifies that incoming SOLICIT can't reuse an existing lease
+// and simply return it, i.e. fake allocation ignores the cache feature.
+// Prefix variant.
+TEST_F(Dhcpv6SrvTest, pdSolicitCache) {
+    NakedDhcpv6Srv srv(0);
+
+    // Enable lease reuse.
+    subnet_->setCacheThreshold(.1);
+
+    const IOAddress prefix("2001:db8:1:2::");
+    const uint8_t prefixlen = pd_pool_->getLength();
+    const uint32_t iaid = 234;
+    const uint32_t pref = subnet_->getPreferred();
+    const uint32_t valid = subnet_->getValid();
+    const int delta = 100;
+    const time_t timestamp = time(NULL) - delta;
+
+    // Generate client-id also duid_.
+    OptionPtr clientid = generateClientId();
+
+    // Check that the prefix we are about to use is indeed in pool.
+    ASSERT_TRUE(subnet_->inPool(Lease::TYPE_PD, prefix));
+
+    Lease6Ptr used(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid, pref, valid,
+                              subnet_->getID(), HWAddrPtr(), prefixlen));
+    used->cltt_ = timestamp;
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+    // Check that the lease is really in the database.
+    Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD, prefix);
+    ASSERT_TRUE(l);
+
+    // Check that preferred, valid and cltt really set.
+    // Constructed lease looks as if it was assigned 100 seconds ago.
+    EXPECT_EQ(l->preferred_lft_, pref);
+    EXPECT_EQ(l->valid_lft_, valid);
+    EXPECT_EQ(l->cltt_, timestamp);
+
+    // Let's create a SOLICIT.
+    Pkt6Ptr sol = Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234));
+    sol->setRemoteAddr(IOAddress("fe80::abcd"));
+    sol->setIface("eth0");
+    sol->setIndex(ETH0_INDEX);
+    sol->addOption(generateIA(D6O_IA_PD, 234, 1500, 3000));
+    sol->addOption(clientid);
+
+    // Pass it to the server and get an advertise
+    AllocEngine::ClientContext6 ctx;
+    bool drop = false;
+    srv.initContext(sol, ctx, drop);
+    ASSERT_FALSE(drop);
+    Pkt6Ptr reply = srv.processSolicit(ctx);
+
+    // check if we get response at all
+    checkResponse(reply, DHCPV6_ADVERTISE, 1234);
+
+    // check that IA_PD was returned and that there's a prefix included
+    boost::shared_ptr<Option6IAPrefix> iapref =
+        checkIA_PD(reply, 234, subnet_->getT1(), subnet_->getT2());
+    ASSERT_TRUE(iapref);
+
+    // Check the prefix.
+    EXPECT_EQ(prefix, iapref->getAddress());
+    EXPECT_EQ(prefixlen, iapref->getLength());
+    EXPECT_EQ(pref, iapref->getPreferred());
+    EXPECT_EQ(valid, iapref->getValid());
+
+    // check DUIDs
+    checkServerId(reply, srv.getServerID());
+    checkClientId(reply, clientid);
+}
+
 // This test verifies that incoming REQUEST can be handled properly, that a
 // REPLY is generated, that the response has an address and that address
 // really belongs to the configured pool.
@@ -1222,6 +1363,147 @@ TEST_F(Dhcpv6SrvTest, ManyRequests) {
     cout << "Assigned address to client3=" << addr3->getAddress() << endl;
 }
 
+// This test verifies that incoming REQUEST can reuse an existing lease.
+TEST_F(Dhcpv6SrvTest, RequestCache) {
+    NakedDhcpv6Srv srv(0);
+
+    // Enable lease reuse.
+    subnet_->setCacheThreshold(.1);
+
+    const IOAddress addr("2001:db8:1:1::cafe:babe");
+    const uint32_t iaid = 234;
+    const uint32_t pref = subnet_->getPreferred();
+    const uint32_t valid = subnet_->getValid();
+    const int delta = 100;
+    const time_t timestamp = time(NULL) - delta;
+
+    // Generate client-id also duid_.
+    OptionPtr clientid = generateClientId();
+
+    // Check that the address we are about to use is indeed in pool.
+    ASSERT_TRUE(subnet_->inPool(Lease::TYPE_NA, addr));
+
+    Lease6Ptr used(new Lease6(Lease::TYPE_NA, addr, duid_, iaid, pref, valid,
+                              subnet_->getID()));
+    used->cltt_ = timestamp;
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+    // Check that the lease is really in the database.
+    Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+    ASSERT_TRUE(l);
+
+    // Check that preferred, valid and cltt really set.
+    // Constructed lease looks as if it was assigned 100 seconds ago.
+    EXPECT_EQ(l->preferred_lft_, pref);
+    EXPECT_EQ(l->valid_lft_, valid);
+    EXPECT_EQ(l->cltt_, timestamp);
+
+    // Let's create a REQUEST.
+    Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
+    req->setRemoteAddr(IOAddress("fe80::abcd"));
+    req->setIface("eth0");
+    req->setIndex(ETH0_INDEX);
+    req->addOption(createIA(Lease::TYPE_NA, addr, 128, iaid));
+    req->addOption(clientid);
+    req->addOption(srv.getServerID());
+
+    // Pass it to the server and get an advertise
+    AllocEngine::ClientContext6 ctx;
+    bool drop = false;
+    srv.initContext(req, ctx, drop);
+    ASSERT_FALSE(drop);
+    Pkt6Ptr reply = srv.processRequest(ctx);
+
+    // check if we get response at all
+    checkResponse(reply, DHCPV6_REPLY, 1234);
+
+    // check that IA_NA was returned and that there's an address included
+    boost::shared_ptr<Option6IAAddr> iaaddr =
+        checkIA_NA(reply, 234, subnet_->getT1(), subnet_->getT2());
+    ASSERT_TRUE(iaaddr);
+
+    // Check the address.
+    EXPECT_EQ(addr, iaaddr->getAddress());
+    EXPECT_EQ(pref - delta, iaaddr->getPreferred());
+    EXPECT_EQ(valid - delta, iaaddr->getValid());
+
+    // check DUIDs
+    checkServerId(reply, srv.getServerID());
+    checkClientId(reply, clientid);
+}
+
+// This test verifies that incoming REQUEST can reuse an existing lease.
+// Prefix variant.
+TEST_F(Dhcpv6SrvTest, pdRequestCache) {
+    NakedDhcpv6Srv srv(0);
+
+    // Enable lease reuse.
+    subnet_->setCacheThreshold(.1);
+
+    const IOAddress prefix("2001:db8:1:2::");
+    const uint8_t prefixlen = pd_pool_->getLength();
+    const uint32_t iaid = 234;
+    const uint32_t pref = subnet_->getPreferred();
+    const uint32_t valid = subnet_->getValid();
+    const int delta = 100;
+    const time_t timestamp = time(NULL) - delta;
+
+    // Generate client-id also duid_.
+    OptionPtr clientid = generateClientId();
+
+    // Check that the prefix we are about to use is indeed in pool.
+    ASSERT_TRUE(subnet_->inPool(Lease::TYPE_PD, prefix));
+
+    Lease6Ptr used(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid, pref, valid,
+                              subnet_->getID(), HWAddrPtr(), prefixlen));
+    used->cltt_ = timestamp;
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+    // Check that the lease is really in the database.
+    Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD, prefix);
+    ASSERT_TRUE(l);
+
+    // Check that preferred, valid and cltt really set.
+    // Constructed lease looks as if it was assigned 100 seconds ago.
+    EXPECT_EQ(l->preferred_lft_, pref);
+    EXPECT_EQ(l->valid_lft_, valid);
+    EXPECT_EQ(l->cltt_, timestamp);
+
+    // Let's create a REQUEST.
+    Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234));
+    req->setRemoteAddr(IOAddress("fe80::abcd"));
+    req->setIface("eth0");
+    req->setIndex(ETH0_INDEX);
+    req->addOption(createIA(Lease::TYPE_PD, prefix, prefixlen, iaid));
+    req->addOption(clientid);
+    req->addOption(srv.getServerID());
+
+    // Pass it to the server and get an advertise
+    AllocEngine::ClientContext6 ctx;
+    bool drop = false;
+    srv.initContext(req, ctx, drop);
+    ASSERT_FALSE(drop);
+    Pkt6Ptr reply = srv.processRequest(ctx);
+
+    // check if we get response at all
+    checkResponse(reply, DHCPV6_REPLY, 1234);
+
+    // check that IA_PD was returned and that there's a prefix included
+    boost::shared_ptr<Option6IAPrefix> iapref =
+        checkIA_PD(reply, 234, subnet_->getT1(), subnet_->getT2());
+    ASSERT_TRUE(iapref);
+
+    // Check the prefix.
+    EXPECT_EQ(prefix, iapref->getAddress());
+    EXPECT_EQ(prefixlen, iapref->getLength());
+    EXPECT_EQ(pref - delta, iapref->getPreferred());
+    EXPECT_EQ(valid - delta, iapref->getValid());
+
+    // check DUIDs
+    checkServerId(reply, srv.getServerID());
+    checkClientId(reply, clientid);
+}
+
 // This test verifies that incoming (positive) RENEW can be handled properly, that a
 // REPLY is generated, that the response has an address and that address
 // really belongs to the configured pool and that lease is actually renewed.
@@ -1365,6 +1647,147 @@ TEST_F(Dhcpv6SrvTest, maxLifetimeReuseExpired) {
                    true, true, 5000, 6000, 4000, 5000);
 }
 
+// This test verifies that incoming RENEW can reuse an existing lease.
+TEST_F(Dhcpv6SrvTest, RenewCache) {
+    NakedDhcpv6Srv srv(0);
+
+    // Enable lease reuse.
+    subnet_->setCacheThreshold(.1);
+
+    const IOAddress addr("2001:db8:1:1::cafe:babe");
+    const uint32_t iaid = 234;
+    const uint32_t pref = subnet_->getPreferred();
+    const uint32_t valid = subnet_->getValid();
+    const int delta = 100;
+    const time_t timestamp = time(NULL) - delta;
+
+    // Generate client-id also duid_.
+    OptionPtr clientid = generateClientId();
+
+    // Check that the address we are about to use is indeed in pool.
+    ASSERT_TRUE(subnet_->inPool(Lease::TYPE_NA, addr));
+
+    Lease6Ptr used(new Lease6(Lease::TYPE_NA, addr, duid_, iaid, pref, valid,
+                              subnet_->getID()));
+    used->cltt_ = timestamp;
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+    // Check that the lease is really in the database.
+    Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA, addr);
+    ASSERT_TRUE(l);
+
+    // Check that preferred, valid and cltt really set.
+    // Constructed lease looks as if it was assigned 100 seconds ago.
+    EXPECT_EQ(l->preferred_lft_, pref);
+    EXPECT_EQ(l->valid_lft_, valid);
+    EXPECT_EQ(l->cltt_, timestamp);
+
+    // Let's create a RENEW.
+    Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234));
+    req->setRemoteAddr(IOAddress("fe80::abcd"));
+    req->setIface("eth0");
+    req->setIndex(ETH0_INDEX);
+    req->addOption(createIA(Lease::TYPE_NA, addr, 128, iaid));
+    req->addOption(clientid);
+    req->addOption(srv.getServerID());
+
+    // Pass it to the server and get an advertise
+    AllocEngine::ClientContext6 ctx;
+    bool drop = false;
+    srv.initContext(req, ctx, drop);
+    ASSERT_FALSE(drop);
+    Pkt6Ptr reply = srv.processRenew(ctx);
+
+    // check if we get response at all
+    checkResponse(reply, DHCPV6_REPLY, 1234);
+
+    // check that IA_NA was returned and that there's an address included
+    boost::shared_ptr<Option6IAAddr> iaaddr =
+        checkIA_NA(reply, 234, subnet_->getT1(), subnet_->getT2());
+    ASSERT_TRUE(iaaddr);
+
+    // Check the address.
+    EXPECT_EQ(addr, iaaddr->getAddress());
+    EXPECT_EQ(pref - delta, iaaddr->getPreferred());
+    EXPECT_EQ(valid - delta, iaaddr->getValid());
+
+    // check DUIDs
+    checkServerId(reply, srv.getServerID());
+    checkClientId(reply, clientid);
+}
+
+// This test verifies that incoming RENEW can reuse an existing lease.
+// Prefix variant.
+TEST_F(Dhcpv6SrvTest, pdRenewCache) {
+    NakedDhcpv6Srv srv(0);
+
+    // Enable lease reuse.
+    subnet_->setCacheThreshold(.1);
+
+    const IOAddress prefix("2001:db8:1:2::");
+    const uint8_t prefixlen = pd_pool_->getLength();
+    const uint32_t iaid = 234;
+    const uint32_t pref = subnet_->getPreferred();
+    const uint32_t valid = subnet_->getValid();
+    const int delta = 100;
+    const time_t timestamp = time(NULL) - delta;
+
+    // Generate client-id also duid_.
+    OptionPtr clientid = generateClientId();
+
+    // Check that the prefix we are about to use is indeed in pool.
+    ASSERT_TRUE(subnet_->inPool(Lease::TYPE_PD, prefix));
+
+    Lease6Ptr used(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid, pref, valid,
+                              subnet_->getID(), HWAddrPtr(), prefixlen));
+    used->cltt_ = timestamp;
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));
+
+    // Check that the lease is really in the database.
+    Lease6Ptr l = LeaseMgrFactory::instance().getLease6(Lease::TYPE_PD, prefix);
+    ASSERT_TRUE(l);
+
+    // Check that preferred, valid and cltt really set.
+    // Constructed lease looks as if it was assigned 100 seconds ago.
+    EXPECT_EQ(l->preferred_lft_, pref);
+    EXPECT_EQ(l->valid_lft_, valid);
+    EXPECT_EQ(l->cltt_, timestamp);
+
+    // Let's create a RENEW.
+    Pkt6Ptr req = Pkt6Ptr(new Pkt6(DHCPV6_RENEW, 1234));
+    req->setRemoteAddr(IOAddress("fe80::abcd"));
+    req->setIface("eth0");
+    req->setIndex(ETH0_INDEX);
+    req->addOption(createIA(Lease::TYPE_PD, prefix, prefixlen, iaid));
+    req->addOption(clientid);
+    req->addOption(srv.getServerID());
+
+    // Pass it to the server and get an advertise
+    AllocEngine::ClientContext6 ctx;
+    bool drop = false;
+    srv.initContext(req, ctx, drop);
+    ASSERT_FALSE(drop);
+    Pkt6Ptr reply = srv.processRenew(ctx);
+
+    // check if we get response at all
+    checkResponse(reply, DHCPV6_REPLY, 1234);
+
+    // check that IA_PD was returned and that there's a prefix included
+    boost::shared_ptr<Option6IAPrefix> iapref =
+        checkIA_PD(reply, 234, subnet_->getT1(), subnet_->getT2());
+    ASSERT_TRUE(iapref);
+
+    // Check the prefix.
+    EXPECT_EQ(prefix, iapref->getAddress());
+    EXPECT_EQ(prefixlen, iapref->getLength());
+    EXPECT_EQ(pref - delta, iapref->getPreferred());
+    EXPECT_EQ(valid - delta, iapref->getValid());
+
+    // check DUIDs
+    checkServerId(reply, srv.getServerID());
+    checkClientId(reply, clientid);
+}
+
 // This test verifies that incoming (positive) RELEASE with address can be
 // handled properly, that a REPLY is generated, that the response has status
 // code and that the lease is indeed removed from the database.
index 2629fcda68671a0550269eb39005adc184bce74d..a3e0a291fcb031abd1121057eb2efb5524b90135 100644 (file)
@@ -2474,6 +2474,188 @@ TEST_F(HooksDhcpv6SrvTest, leases6CommittedRequestPrefix) {
     checkCalloutHandleReset(client.getContext().query_);
 }
 
+// This test verifies that the callout installed on the leases6_committed hook
+// point is executed as a result of REQUEST message sent to reuse an
+// existing lease.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedCache) {
+    IfaceMgrTestConfig test_config(true);
+
+    string config = "{ \"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1::/64\" } ],"
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"interface\": \"eth1\", "
+        "    \"cache-threshold\": .25 "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    Dhcp6Client client;
+    client.setInterface("eth1");
+    client.requestAddress(0xabca, IOAddress("2001:db8:1::28"));
+
+    ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+    ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                    "leases6_committed", leases6_committed_callout));
+
+    ASSERT_NO_THROW(client.doSARR());
+
+    // Make sure that we received a response
+    ASSERT_TRUE(client.getContext().response_);
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("leases6_committed", callback_name_);
+
+    // Check if all expected parameters were really received
+    vector<string> expected_argument_names;
+    expected_argument_names.push_back("query6");
+    expected_argument_names.push_back("deleted_leases6");
+    expected_argument_names.push_back("leases6");
+
+    sort(expected_argument_names.begin(), expected_argument_names.end());
+    EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+    // Newly allocated lease should be returned.
+    ASSERT_TRUE(callback_new_leases6_);
+    ASSERT_EQ(1, callback_new_leases6_->size());
+    Lease6Ptr lease = callback_new_leases6_->at(0);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("2001:db8:1::28", lease->addr_.toText());
+
+    // Deleted lease must not be present, because it is a new allocation.
+    ASSERT_TRUE(callback_deleted_leases6_);
+    EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
+
+    // Check if the callout handle state was reset after the callout.
+    checkCalloutHandleReset(client.getContext().query_);
+
+    resetCalloutBuffers();
+
+    // Request the lease and make sure that the callout has been executed.
+    ASSERT_NO_THROW(client.doRequest());
+
+    // Make sure that we received a response
+    ASSERT_TRUE(client.getContext().response_);
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("leases6_committed", callback_name_);
+
+    // Requested lease should not be present, because it is reused.
+    ASSERT_TRUE(callback_new_leases6_);
+    EXPECT_TRUE(callback_new_leases6_->empty());
+
+    // Deleted lease must not be present, because it is renewed.
+    ASSERT_TRUE(callback_deleted_leases6_);
+    EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
+
+    // Check if the callout handle state was reset after the callout.
+    checkCalloutHandleReset(client.getContext().query_);
+}
+
+// This test verifies that the callout installed on the leases6_committed hook
+// point is executed as a result of REQUEST message sent to reuse an
+// existing lease. Prefix variant.
+TEST_F(HooksDhcpv6SrvTest, leases6CommittedCachePrefix) {
+    IfaceMgrTestConfig test_config(true);
+
+    string config = "{ \"interfaces-config\": {"
+        "  \"interfaces\": [ \"*\" ]"
+        "},"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pd-pools\": [ {"
+        "        \"prefix\": \"2001:db8:1::\", "
+        "        \"prefix-len\": 56, "
+        "        \"delegated-len\": 64 } ], "
+        "    \"subnet\": \"2001:db8:1::/48\", "
+        "    \"interface\": \"eth1\", "
+        "    \"cache-threshold\": .25 "
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    Dhcp6Client client;
+    client.setInterface("eth1");
+    client.requestPrefix(0xabca, 64, IOAddress("2001:db8:1:28::"));
+
+    ASSERT_NO_THROW(configure(config, *client.getServer()));
+
+    ASSERT_NO_THROW(HooksManager::preCalloutsLibraryHandle().registerCallout(
+                    "leases6_committed", leases6_committed_callout));
+
+    ASSERT_NO_THROW(client.doSARR());
+
+    // Make sure that we received a response
+    ASSERT_TRUE(client.getContext().response_);
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("leases6_committed", callback_name_);
+
+    // Check if all expected parameters were really received
+    vector<string> expected_argument_names;
+    expected_argument_names.push_back("query6");
+    expected_argument_names.push_back("deleted_leases6");
+    expected_argument_names.push_back("leases6");
+
+    sort(expected_argument_names.begin(), expected_argument_names.end());
+    EXPECT_TRUE(callback_argument_names_ == expected_argument_names);
+
+    // Newly allocated lease should be returned.
+    ASSERT_TRUE(callback_new_leases6_);
+    ASSERT_EQ(1, callback_new_leases6_->size());
+    Lease6Ptr lease = callback_new_leases6_->at(0);
+    ASSERT_TRUE(lease);
+    EXPECT_EQ("2001:db8:1:28::", lease->addr_.toText());
+    EXPECT_EQ(64, lease->prefixlen_);
+
+    // Deleted lease must not be present, because it is a new allocation.
+    ASSERT_TRUE(callback_deleted_leases6_);
+    EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
+
+    // Check if the callout handle state was reset after the callout.
+    checkCalloutHandleReset(client.getContext().query_);
+
+    resetCalloutBuffers();
+
+    // Request the lease and make sure that the callout has been executed.
+    ASSERT_NO_THROW(client.doRequest());
+
+    // Make sure that we received a response
+    ASSERT_TRUE(client.getContext().response_);
+
+    // Check that the callback called is indeed the one we installed
+    EXPECT_EQ("leases6_committed", callback_name_);
+
+    // Requested lease should not be present, because it is reused.
+    ASSERT_TRUE(callback_new_leases6_);
+    EXPECT_TRUE(callback_new_leases6_->empty());
+
+    // Deleted lease must not be present, because it is renewed.
+    ASSERT_TRUE(callback_deleted_leases6_);
+    EXPECT_TRUE(callback_deleted_leases6_->empty());
+
+    // Pkt passed to a callout must be configured to copy retrieved options.
+    EXPECT_TRUE(callback_qry_options_copy_);
+
+    // Check if the callout handle state was reset after the callout.
+    checkCalloutHandleReset(client.getContext().query_);
+}
+
 // This test verifies that it is possible to park a packet as a result of
 // the leases6_committed callouts.
 TEST_F(HooksDhcpv6SrvTest, leases6CommittedParkRequests) {
index 01872fd9d282fd93abd8ddb67ae24bdfeaf0a08b..b5453614c24e84f287217f346622b9319d85c344 100644 (file)
@@ -272,6 +272,14 @@ was made. The client will be offered/allocated a reserved address
 the next time it retries sending a DHCPDISCOVER/DHCPREQUEST message to
 the server.
 
+@subsection allocEngineReuse Allocation Engine Cache
+
+The allocation engine provides a cache-like feature: when a suitable
+lease already exists for a client if its age is small enough compared
+to the valid lifetime (threshold parameter) and below a configured maximum
+(max age parameter) the lease can be reused. A reusable lease is marked
+by a not zero remaining_valid_lft_ value.
+
 @section timerManager Timer Manager
 
 The @c isc::dhcp::TimerMgr is a singleton class used throughout the
index f557f17d47fabf75bba0eba9591f31e35cae4eac..9f3ce773d6a52a1c8310563f22d74de66263c1d5 100644 (file)
@@ -4084,6 +4084,9 @@ TEST_F(AllocEngine4Test, requestCacheBadMaxAge4) {
     // Set valid lifetime to 500.
     subnet_->setValid(500);
 
+    // Set the threshold to 25%.
+    subnet_->setCacheThreshold(.25);
+
     // Set the max age to 50.
     subnet_->setCacheMaxAge(50);
 
@@ -4202,6 +4205,9 @@ TEST_F(AllocEngine4Test, discoverCacheRevDDNS4) {
     // Set the threshold to 10%.
     subnet_->setCacheThreshold(.1);
 
+    // Set the max age to 200.
+    subnet_->setCacheMaxAge(200);
+
     IOAddress addr("192.0.2.105");
     time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
     Lease4Ptr lease(new Lease4(addr, hwaddr_, clientid_,
index 940c6718f155fe7223e1661286c63f5d7b769287..ece87a1dc05c6a656299857cf6e8268fcf369be8 100644 (file)
@@ -4394,8 +4394,46 @@ TEST_F(AllocEngine6ExtendedInfoTest, reuseExpiredLease6) {
                 << "  actual: " << *(lease->getContext()) << std::endl;
 }
 
-// V6 engine does not allocate on fake allocation / solicit so the cache
-// feature does nothing visible in this case.
+// Checks whether fake allocation does not use the cache feature.
+TEST_F(AllocEngine6Test, solicitNoCache) {
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    // Set the threshold to 25%.
+    subnet_->setCacheThreshold(.25);
+
+    IOAddress addr("2001:db8:1::15");
+    time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_,
+                               300, 400, subnet_->getID()));
+    lease->cltt_ = now;
+    ASSERT_FALSE(lease->expired());
+    // Copy the lease, so as it can be compared with.
+    Lease6Ptr original_lease(new Lease6(*lease));
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+    // Create a context for fake allocation..
+    Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
+    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", true,
+                                    query);
+    ctx.currentIA().iaid_ = iaid_;
+    ctx.currentIA().addHint(addr);
+
+    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+    EXPECT_EQ(addr, lease->addr_);
+    EXPECT_EQ(128, lease->prefixlen_);
+
+    // The lease was not reused.
+    EXPECT_EQ(0, lease->remaining_valid_lft_);
+
+    // Check the lease was not updated in the database.
+    Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+                                                               lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    detailCompareLease(original_lease, from_mgr);
+}
 
 // Checks whether a lease can be reused (request) using cache threshold.
 TEST_F(AllocEngine6Test, requestCacheThreshold6) {
@@ -4718,6 +4756,9 @@ TEST_F(AllocEngine6Test, requestCacheBadThreshold6) {
     // Set the threshold to 10%.
     subnet_->setCacheThreshold(.1);
 
+    // Set the max age to 150.
+    subnet_->setCacheMaxAge(150);
+
     IOAddress addr("2001:db8:1::15");
     time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
     Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_,
@@ -4758,6 +4799,98 @@ TEST_F(AllocEngine6Test, renewCacheBadThreshold6) {
     // Set the threshold to 10%.
     subnet_->setCacheThreshold(.1);
 
+    // Set the max age to 150.
+    subnet_->setCacheMaxAge(150);
+
+    IOAddress prefix("2001:db8:1:2::");
+    uint8_t prefixlen = 80;
+    time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+    Lease6Ptr lease(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid_,
+                               300, 400, subnet_->getID(),
+                               HWAddrPtr(), prefixlen));
+    lease->cltt_ = now;
+    ASSERT_FALSE(lease->expired());
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+    // Create a context for renew.
+    Pkt6Ptr query(new Pkt6(DHCPV6_RENEW, 1234));
+    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+                                    query);
+    ctx.currentIA().type_ = Lease::TYPE_PD;
+    ctx.currentIA().iaid_ = iaid_;
+    ctx.currentIA().addHint(prefix, prefixlen);
+
+    EXPECT_NO_THROW(lease = expectOneLease(engine->renewLeases6(ctx)));
+    EXPECT_EQ(prefix, lease->addr_);
+    EXPECT_EQ(prefixlen, lease->prefixlen_);
+
+    // The lease was not reused.
+    EXPECT_EQ(0, lease->remaining_valid_lft_);
+
+    // Check the lease was updated in the database.
+    Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+                                                               lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    detailCompareLease(lease, from_mgr);
+}
+
+// Checks whether a lease can't be reused (request) using too small
+// cache max age.
+TEST_F(AllocEngine6Test, requestCacheBadMaxAge6) {
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    // Set the threshold to 25%.
+    subnet_->setCacheThreshold(.25);
+
+    // Set the max age to 50.
+    subnet_->setCacheMaxAge(50);
+
+    IOAddress addr("2001:db8:1::15");
+    time_t now = time(NULL) - 100; // Allocated 100 seconds ago.
+    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_,
+                               300, 400, subnet_->getID()));
+    lease->cltt_ = now;
+    ASSERT_FALSE(lease->expired());
+    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
+
+    // Create a context for request.
+    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
+    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
+                                    query);
+    ctx.currentIA().iaid_ = iaid_;
+    ctx.currentIA().addHint(addr);
+
+    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
+    EXPECT_EQ(addr, lease->addr_);
+    EXPECT_EQ(128, lease->prefixlen_);
+
+    // The lease was not reused.
+    EXPECT_EQ(0, lease->remaining_valid_lft_);
+
+    // Check the lease was updated in the database.
+    Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
+                                                               lease->addr_);
+    ASSERT_TRUE(from_mgr);
+
+    detailCompareLease(lease, from_mgr);
+}
+
+// Checks whether a lease can't be reused (renew) using too small
+// cache max age.
+TEST_F(AllocEngine6Test, renewCacheBadMaxAge6) {
+    boost::scoped_ptr<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    // Set the threshold to 25%.
+    subnet_->setCacheThreshold(.25);
+
+    // Set the max age to 50.
+    subnet_->setCacheMaxAge(50);
+
     IOAddress prefix("2001:db8:1:2::");
     uint8_t prefixlen = 80;
     time_t now = time(NULL) - 100; // Allocated 100 seconds ago.