// Copyright (C) 2015-2025 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#include <config.h>
#include <dhcp/pkt4.h>
#include <dhcp/pkt6.h>
#include <dhcpsrv/allocator.h>
#include <dhcpsrv/host_mgr.h>
#include <dhcpsrv/parsers/client_class_def_parser.h>
#include <dhcpsrv/testutils/alloc_engine_utils.h>
#include <dhcpsrv/testutils/test_utils.h>
#include <eval/eval_context.h>
#include <stats/stats_mgr.h>
#include <testutils/gtest_utils.h>
#include <boost/pointer_cast.hpp>

using namespace std;
using namespace isc::hooks;
using namespace isc::asiolink;
using namespace isc::stats;
using namespace isc::data;
using namespace isc::util;

namespace isc {
namespace dhcp {
namespace test {

// Convenience values to improve readibility.
const bool IN_SUBNET = true;
const bool IN_POOL = true;

// Test convenience method adding hints to IA context.
TEST(ClientContext6Test, addHint) {
   AllocEngine::ClientContext6 ctx;
   ctx.currentIA().addHint(IOAddress("2001:db8:1::1"));
   ctx.currentIA().addHint(IOAddress("3000:1::"), 64);
   ctx.currentIA().addHint(IOAddress("3001:2::"), 64, 100, 200);

   ASSERT_EQ(3, ctx.currentIA().hints_.size());
   EXPECT_EQ("2001:db8:1::1", ctx.currentIA().hints_[0].getAddress().toText());
   EXPECT_EQ("3000:1::", ctx.currentIA().hints_[1].getAddress().toText());
   EXPECT_EQ("3001:2::", ctx.currentIA().hints_[2].getAddress().toText());
   EXPECT_EQ(100, ctx.currentIA().hints_[2].getPreferred());
   EXPECT_EQ(200, ctx.currentIA().hints_[2].getValid());
}

// Test convenience method adding allocated prefixes and addresses to
// a context.
TEST(ClientContext6Test, addAllocatedResource) {
   AllocEngine::ClientContext6 ctx;
   ctx.addAllocatedResource(IOAddress("2001:db8:1::1"));
   ctx.addAllocatedResource(IOAddress("3000:1::"), 64);

   ASSERT_EQ(2, ctx.allocated_resources_.size());
   EXPECT_TRUE(ctx.isAllocated(IOAddress("2001:db8:1::1")));
   EXPECT_TRUE(ctx.isAllocated(IOAddress("3000:1::"), 64));
}

// This test checks if the v6 Allocation Engine can be instantiated, parses
// parameters string and allocators are created.
TEST_F(AllocEngine6Test, constructor) {
    boost::scoped_ptr<AllocEngine> x;

    ASSERT_NO_THROW(x.reset(new AllocEngine(100)));
}

// This test checks if two simple IPv6 allocations succeed and that the
// statistics is properly updated. Prior to the second allocation it
// resets the pointer to the last allocated address within the address
// pool. This causes the engine to walk over the already allocated
// address and then pick the first available address for the second
// allocation. Because the allocation engine checks the callouts next
// step status after each attempt to allocate an address, this test
// also sets this status to non-default value prior to the second
// allocation attempt, to make sure that this unexpected status will
// not interfere with the allocation.
TEST_F(AllocEngine6Test, simpleAlloc6) {
    // Assigned count should be zero.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));

    // Get the cumulative count of assigned addresses.
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    simpleAlloc6Test(pool_, IOAddress("::"), false);

    // We should have bumped the assigned counter by 1
    EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
    cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    glbl_cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));

    // Reset last allocated address to check that the other client will
    // be refused the already allocated address and will get the one
    // available.
    auto allocation_state = boost::dynamic_pointer_cast<PoolIterativeAllocationState>(pool_->getAllocationState());
    allocation_state->resetLastAllocated();

    // Simulate another client. This client should be assigned a different
    // address.
    DuidPtr duid(new DUID(std::vector<uint8_t>(8, 0x84)));
    simpleAlloc6Test(pool_, duid, IOAddress("::"), false);

    // We should have bumped the assigned counter by 2
    EXPECT_TRUE(testStatistics("assigned-nas", 2, subnet_->getID()));
    cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    glbl_cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
}

// This test checks that simple allocation uses default lifetimes.
TEST_F(AllocEngine6Test, defaultAlloc6) {
    simpleAlloc6Test(pool_, IOAddress("::"), 0, 0, 300, 400);
}

// This test checks that simple allocation uses specified lifetimes.
TEST_F(AllocEngine6Test, hintAlloc6) {
    simpleAlloc6Test(pd_pool_, IOAddress("::"), 301, 399, 301, 399);
}

// This test checks that simple allocation uses min lifetimes.
TEST_F(AllocEngine6Test, minAlloc6) {
    simpleAlloc6Test(pool_, IOAddress("::"), 100, 200, 200, 300);
}

// This test checks that simple allocation uses max lifetimes.
TEST_F(AllocEngine6Test, maxAlloc6) {
    simpleAlloc6Test(pd_pool_, IOAddress("::"), 500, 600, 400, 500);
}

// This test checks if the simple PD allocation (REQUEST) can succeed
// and the stats counter is properly bumped by 1
TEST_F(AllocEngine6Test, pdSimpleAlloc6) {
    // Assigned count should be zero.
    EXPECT_TRUE(testStatistics("assigned-pds", 0, subnet_->getID()));

    // Get the cumulative count of assigned prefixes.
    int64_t cumulative = getStatistics("cumulative-assigned-pds",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-pds");

    simpleAlloc6Test(pd_pool_, IOAddress("::"), false);

    // We should have bumped the assigned counter by 1
    EXPECT_TRUE(testStatistics("assigned-pds", 1, subnet_->getID()));
    cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-pds",
                               cumulative, subnet_->getID()));
    glbl_cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-pds", glbl_cumulative));
}

// This test checks if the fake allocation (for SOLICIT) can succeed
// and the stats counter isn't bumped
TEST_F(AllocEngine6Test, fakeAlloc6) {
    // Assigned count should be zero.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));

    // Get the cumulative count of assigned addresses.
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    simpleAlloc6Test(pool_, IOAddress("::"), true);

    // We should not have bumped the assigned counter.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
}

// This test checks if the fake PD allocation (for SOLICIT) can succeed
// and the stats counter isn't bumped
TEST_F(AllocEngine6Test, pdFakeAlloc6) {
    // Assigned count should be zero.
    EXPECT_TRUE(testStatistics("assigned-pds", 0, subnet_->getID()));

    // Get the cumulative count of assigned prefixes.
    int64_t cumulative = getStatistics("cumulative-assigned-pds",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-pds");

    simpleAlloc6Test(pd_pool_, IOAddress("::"), true);

    // We should not have bumped the assigned counter
    EXPECT_TRUE(testStatistics("assigned-pds", 0, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-pds",
                               cumulative, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-pds", glbl_cumulative));
}

// This test checks if the allocation with a hint that is valid (in range,
// in pool and free) can succeed
TEST_F(AllocEngine6Test, allocWithValidHint6) {

    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::15"),
                                       false);
    ASSERT_TRUE(lease);

    // We should get what we asked for
    EXPECT_EQ("2001:db8:1::15", lease->addr_.toText());
}

// This test checks if the address allocation with a hint that is in range,
// in pool, but is currently used, can succeed
TEST_F(AllocEngine6Test, allocWithUsedHint6) {
    allocWithUsedHintTest(Lease::TYPE_NA,
                          IOAddress("2001:db8:1::1f"), // allocate this as used
                          IOAddress("2001:db8:1::1f"), // request this addr
                          128);
}

// This test checks if the PD allocation with a hint that is in range,
// in pool, but is currently used, can succeed
TEST_F(AllocEngine6Test, pdAllocWithUsedHint6) {
    allocWithUsedHintTest(Lease::TYPE_PD,
                          IOAddress("2001:db8:1:2::"), // allocate this prefix as used
                          IOAddress("2001:db8:1:2::"), // request this prefix
                          80);
}

// This test checks if the allocation with a hint that is out the blue
// can succeed. The invalid hint should be ignored completely.
TEST_F(AllocEngine6Test, allocBogusHint6) {

    allocBogusHint6(Lease::TYPE_NA, IOAddress("3000::abc"), 128);
}

// This test checks if the allocation with a hint that is out the blue
// can succeed. The invalid hint should be ignored completely.
TEST_F(AllocEngine6Test, pdAllocBogusHint6) {

    allocBogusHint6(Lease::TYPE_PD, IOAddress("3000::abc"), 80);
}

// This test checks that null values are handled properly
TEST_F(AllocEngine6Test, allocateAddress6Nulls) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    // Allocations without subnet are not allowed
    Lease6Ptr lease;
    AllocEngine::ClientContext6 ctx1(Subnet6Ptr(), duid_, false, false, "", false,
                                     Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
    ctx1.currentIA().iaid_ = iaid_;

    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx1)));
    ASSERT_FALSE(lease);

    EXPECT_EQ(0, getStatistics("v6-allocation-fail"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes"));

    EXPECT_EQ(0, getStatistics("v6-allocation-fail", subnet_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network", subnet_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet", subnet_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", subnet_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", subnet_->getID()));

    // Allocations without DUID are not allowed either
    AllocEngine::ClientContext6 ctx2(subnet_, DuidPtr(), false, false, "", false,
                                     Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
    ctx2.currentIA().iaid_ = iaid_;
    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx2)));
    ASSERT_FALSE(lease);

    EXPECT_EQ(0, getStatistics("v6-allocation-fail"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes"));

    EXPECT_EQ(0, getStatistics("v6-allocation-fail", subnet_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network", subnet_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet", subnet_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", subnet_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", subnet_->getID()));
}

// This test checks if really small pools are working
TEST_F(AllocEngine6Test, smallPool6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    IOAddress addr("2001:db8:1::ad");

    // Create a subnet with a pool that has one address.
    initSubnet(IOAddress("2001:db8:1::"), addr, addr);

    // Initialize FQDN for a lease.
    initFqdn("myhost.example.com", true, true);

    Lease6Ptr lease;
    AllocEngine::ClientContext6 ctx(subnet_, duid_, fqdn_fwd_, fqdn_rev_,
                                    hostname_, false,
                                    Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
    ctx.currentIA().iaid_ = iaid_;
    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));

    // Check that we got that single lease
    ASSERT_TRUE(lease);

    EXPECT_EQ("2001:db8:1::ad", lease->addr_.toText());

    // Do all checks on the lease
    checkLease6(duid_, lease, Lease::TYPE_NA, 128);

    // Check that the lease is indeed in LeaseMgr
    Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_,
                                                               lease->addr_);
    ASSERT_TRUE(from_mgr);

    // Now check that the lease in LeaseMgr has the same parameters
    detailCompareLease(lease, from_mgr);

    // This is a new lease allocation. The old lease corresponding to a newly
    // allocated lease should be null.
    ASSERT_TRUE(ctx.currentIA().old_leases_.empty());
}

// This test checks if all addresses in a pool are currently used, the attempt
// to find out a new lease fails.
TEST_F(AllocEngine6Test, outOfAddresses6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    IOAddress addr("2001:db8:1::ad");
    CfgMgr& cfg_mgr = CfgMgr::instance();
    cfg_mgr.clear(); // Get rid of the default test configuration

    // Create configuration similar to other tests, but with a single address pool
    subnet_ = Subnet6::create(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4, SubnetID(10));
    pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, addr, addr)); // just a single address
    subnet_->addPool(pool_);
    cfg_mgr.getStagingCfg()->getCfgSubnets6()->add(subnet_);

    // Just a different duid
    DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
    const uint32_t other_iaid = 3568;
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, other_duid, other_iaid,
                               501, 502, subnet_->getID(), HWAddrPtr()));
    lease->cltt_ = time(0) - 10; // Allocated 10 seconds ago
    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    // There is just a single address in the pool and allocated it to someone
    // else, so the allocation should fail
    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
                                    query);
    ctx.currentIA().iaid_ = iaid_;

    Lease6Ptr lease2;
    EXPECT_NO_THROW(lease2 = expectOneLease(engine->allocateLeases6(ctx)));
    ASSERT_FALSE(lease2);

    EXPECT_EQ(1, getStatistics("v6-allocation-fail"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network"));
    EXPECT_EQ(1, getStatistics("v6-allocation-fail-subnet"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes"));

    EXPECT_EQ(1, getStatistics("v6-allocation-fail", subnet_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network", subnet_->getID()));
    EXPECT_EQ(1, getStatistics("v6-allocation-fail-subnet", subnet_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", subnet_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", subnet_->getID()));
}

// This test checks if an expired lease can be reused in SOLICIT (fake allocation)
TEST_F(AllocEngine6Test, solicitReuseExpiredLease6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    IOAddress addr("2001:db8:1::ad");

    // Create one subnet with a pool holding one address.
    initSubnet(IOAddress("2001:db8:1::"), addr, addr);

    // Initialize FQDN data for the lease.
    initFqdn("myhost.example.com", true, true);

    // Verify the all of relevant stats are zero.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
    EXPECT_TRUE(testStatistics("reclaimed-leases", 0));
    EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID()));

    // Get the cumulative count of assigned addresses.
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    // Just a different duid
    DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
    const uint32_t other_iaid = 3568;
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, other_duid, other_iaid,
                               501, 502, subnet_->getID(), HWAddrPtr()));
    lease->cltt_ = time(0) - 500; // Allocated 500 seconds ago
    lease->valid_lft_ = 495; // Lease was valid for 495 seconds
    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    // Make sure that we really created expired lease
    ASSERT_TRUE(lease->expired());

    // CASE 1: Asking for any address
    AllocEngine::ClientContext6 ctx1(subnet_, duid_, fqdn_fwd_, fqdn_rev_, hostname_,
                                     true, Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)));
    ctx1.currentIA().iaid_ = iaid_;

    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx1)));

    // Check that we got that single lease
    ASSERT_TRUE(lease);
    EXPECT_EQ(addr, lease->addr_);

    // Do all checks on the lease (if subnet-id, preferred/valid times are ok etc.)
    checkLease6(duid_, lease, Lease::TYPE_NA, 128);

    // CASE 2: Asking specifically for this address
    AllocEngine::ClientContext6 ctx2(subnet_, duid_, false, false, "", true,
                                     Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
    ctx2.currentIA().iaid_ = iaid_;
    ctx2.currentIA().addHint(addr);

    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx2)));

    // Check that we got that single lease
    ASSERT_TRUE(lease);
    EXPECT_EQ(addr, lease->addr_);

    // Verify the none of relevant stats were altered.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
    EXPECT_TRUE(testStatistics("reclaimed-leases", 0));
    EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID()));
}

// This test checks if an expired lease can be reused using default lifetimes.
TEST_F(AllocEngine6Test, defaultReuseExpiredLease6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    IOAddress addr("2001:db8:1::ad");

    // Create one subnet with a pool holding one address.
    initSubnet(IOAddress("2001:db8:1::"), addr, addr);
    subnet_->setPreferred(Triplet<uint32_t>(200, 300, 400));
    subnet_->setValid(Triplet<uint32_t>(300, 400, 500));

    // Initialize FQDN data for the lease.
    initFqdn("myhost.example.com", true, true);

    // Just a different duid
    DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
    const uint32_t other_iaid = 3568;
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, other_duid, other_iaid,
                               501, 502, subnet_->getID(), HWAddrPtr()));
    lease->cltt_ = time(0) - 500; // Allocated 500 seconds ago
    lease->valid_lft_ = 495; // Lease was valid for 495 seconds
    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    // Make sure that we really created expired lease
    ASSERT_TRUE(lease->expired());

    // Asking specifically for this address with zero lifetimes
    AllocEngine::ClientContext6 ctx2(subnet_, duid_, false, false, "", true,
                                     Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
    ctx2.currentIA().iaid_ = iaid_;
    ctx2.currentIA().addHint(addr, 128, 0, 0);

    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx2)));

    // Check that we got that single lease
    ASSERT_TRUE(lease);
    EXPECT_EQ(addr, lease->addr_);

    // Check lifetimes: defaults are expected.
    EXPECT_EQ(300, lease->preferred_lft_);
    EXPECT_EQ(400, lease->valid_lft_);
}

// This test checks if an expired lease can be reused using specified lifetimes.
TEST_F(AllocEngine6Test, hintReuseExpiredLease6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    IOAddress addr("2001:db8:1::ad");

    // Create one subnet with a pool holding one address.
    initSubnet(IOAddress("2001:db8:1::"), addr, addr);
    subnet_->setPreferred(Triplet<uint32_t>(200, 300, 400));
    subnet_->setValid(Triplet<uint32_t>(300, 400, 500));

    // Initialize FQDN data for the lease.
    initFqdn("myhost.example.com", true, true);

    // Just a different duid
    DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
    const uint32_t other_iaid = 3568;
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, other_duid, other_iaid,
                               501, 502, subnet_->getID(), HWAddrPtr()));
    lease->cltt_ = time(0) - 500; // Allocated 500 seconds ago
    lease->valid_lft_ = 495; // Lease was valid for 495 seconds
    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    // Make sure that we really created expired lease
    ASSERT_TRUE(lease->expired());

    // Asking specifically for this address with zero lifetimes
    AllocEngine::ClientContext6 ctx2(subnet_, duid_, false, false, "", true,
                                     Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
    ctx2.currentIA().iaid_ = iaid_;
    ctx2.currentIA().addHint(addr, 128, 299, 401);

    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx2)));

    // Check that we got that single lease
    ASSERT_TRUE(lease);
    EXPECT_EQ(addr, lease->addr_);

    // Check lifetimes: specified values are expected.
    EXPECT_EQ(299, lease->preferred_lft_);
    EXPECT_EQ(401, lease->valid_lft_);
}

// This test checks if an expired lease can be reused using min lifetimes.
TEST_F(AllocEngine6Test, minReuseExpiredLease6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    IOAddress addr("2001:db8:1::ad");

    // Create one subnet with a pool holding one address.
    initSubnet(IOAddress("2001:db8:1::"), addr, addr);
    subnet_->setPreferred(Triplet<uint32_t>(200, 300, 400));
    subnet_->setValid(Triplet<uint32_t>(300, 400, 500));

    // Initialize FQDN data for the lease.
    initFqdn("myhost.example.com", true, true);

    // Just a different duid
    DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
    const uint32_t other_iaid = 3568;
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, other_duid, other_iaid,
                               501, 502, subnet_->getID(), HWAddrPtr()));
    lease->cltt_ = time(0) - 500; // Allocated 500 seconds ago
    lease->valid_lft_ = 495; // Lease was valid for 495 seconds
    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    // Make sure that we really created expired lease
    ASSERT_TRUE(lease->expired());

    // Asking specifically for this address with zero lifetimes
    AllocEngine::ClientContext6 ctx2(subnet_, duid_, false, false, "", true,
                                     Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
    ctx2.currentIA().iaid_ = iaid_;
    ctx2.currentIA().addHint(addr, 128, 100, 200);

    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx2)));

    // Check that we got that single lease
    ASSERT_TRUE(lease);
    EXPECT_EQ(addr, lease->addr_);

    // Check lifetimes: min values are expected.
    EXPECT_EQ(200, lease->preferred_lft_);
    EXPECT_EQ(300, lease->valid_lft_);
}

// This test checks if an expired lease can be reused using max lifetimes.
TEST_F(AllocEngine6Test, maxReuseExpiredLease6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    IOAddress addr("2001:db8:1::ad");

    // Create one subnet with a pool holding one address.
    initSubnet(IOAddress("2001:db8:1::"), addr, addr);
    subnet_->setPreferred(Triplet<uint32_t>(200, 300, 400));
    subnet_->setValid(Triplet<uint32_t>(300, 400, 500));

    // Initialize FQDN data for the lease.
    initFqdn("myhost.example.com", true, true);

    // Just a different duid
    DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
    const uint32_t other_iaid = 3568;
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, other_duid, other_iaid,
                               501, 502, subnet_->getID(), HWAddrPtr()));
    lease->cltt_ = time(0) - 500; // Allocated 500 seconds ago
    lease->valid_lft_ = 495; // Lease was valid for 495 seconds
    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    // Make sure that we really created expired lease
    ASSERT_TRUE(lease->expired());

    // Asking specifically for this address with zero lifetimes
    AllocEngine::ClientContext6 ctx2(subnet_, duid_, false, false, "", true,
                    Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
    ctx2.currentIA().iaid_ = iaid_;
    ctx2.currentIA().addHint(addr, 128, 500, 600);

    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx2)));

    // Check that we got that single lease
    ASSERT_TRUE(lease);
    EXPECT_EQ(addr, lease->addr_);

    // Check lifetimes: max values are expected.
    EXPECT_EQ(400, lease->preferred_lft_);
    EXPECT_EQ(500, lease->valid_lft_);
}

// This test checks if an expired lease can be reused using class lifetimes.
// Verifies getLifetimes() is invoked by v6 resuseExpiredLease().
TEST_F(AllocEngine6Test, classReuseExpiredLease6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    IOAddress addr("2001:db8:1::ad");

    // Let's make a classes with valid-lifetime and add it to the dictionary.
    ClientClassDictionaryPtr dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary();

    ClientClassDefPtr class_def(new ClientClassDef("valid_one", ExpressionPtr()));
    Triplet<uint32_t> valid_one(600, 700, 800);
    class_def->setValid(valid_one);
    dictionary->addClass(class_def);

    // Create one subnet with a pool holding one address.
    initSubnet(IOAddress("2001:db8:1::"), addr, addr);
    subnet_->setPreferred(Triplet<uint32_t>(100, 200, 300));
    subnet_->setValid(Triplet<uint32_t>(300, 400, 500));

    // Initialize FQDN data for the lease.
    initFqdn("myhost.example.com", true, true);

    // Just a different duid
    DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
    const uint32_t other_iaid = 3568;
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, other_duid, other_iaid,
                               501, 502, subnet_->getID(), HWAddrPtr()));
    lease->cltt_ = time(0) - 500; // Allocated 500 seconds ago
    lease->valid_lft_ = 495; // Lease was valid for 495 seconds
    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    // Make sure that we really created expired lease
    ASSERT_TRUE(lease->expired());

    // Asking specifically for this address with zero lifetimes
    AllocEngine::ClientContext6 ctx2(subnet_, duid_, false, false, "", true,
                                     Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
    ctx2.currentIA().iaid_ = iaid_;
    ctx2.currentIA().addHint(addr, 128, 0, 0);

    // Add the class name to the context.
    ctx2.query_->addClass("valid_one");

    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx2)));

    // Check that we got that single lease
    ASSERT_TRUE(lease);
    EXPECT_EQ(addr, lease->addr_);

    // Check lifetimes: specified values are expected.
    EXPECT_EQ(200, lease->preferred_lft_);
    EXPECT_EQ(700, lease->valid_lft_);
}

// This test checks an expired registered lease can't be reused in SOLICT.
TEST_F(AllocEngine6Test, solicitReuseExpiredRegisteredLease6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    IOAddress addr("2001:db8:1::ad");

    // Create one subnet with a pool holding one address.
    initSubnet(IOAddress("2001:db8:1::"), addr, addr);

    // Initialize FQDN data for the lease.
    initFqdn("myhost.example.com", true, true);

    // Just a different duid
    DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
    const uint32_t other_iaid = 3568;
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, other_duid, other_iaid,
                               501, 502, subnet_->getID(), HWAddrPtr()));
    lease->state_ = Lease::STATE_REGISTERED;
    lease->cltt_ = time(0) - 500; // Registered 500 seconds ago
    lease->valid_lft_ = 495; // Lease was valid for 495 seconds
    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    // Make sure that we really created expired lease
    ASSERT_TRUE(lease->expired());

    // CASE 1: Asking for any address
    AllocEngine::ClientContext6 ctx1(subnet_, duid_, fqdn_fwd_, fqdn_rev_, hostname_,
                                     true, Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)));
    ctx1.currentIA().iaid_ = iaid_;

    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx1)));

    // Check that we did not get this single lease.
    EXPECT_FALSE(lease);

    // CASE 2: Asking specifically for this address
    AllocEngine::ClientContext6 ctx2(subnet_, duid_, false, false, "", true,
                                     Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
    ctx2.currentIA().iaid_ = iaid_;
    ctx2.currentIA().addHint(addr);

    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx2)));

    // Check that we did not get this single lease.
    EXPECT_FALSE(lease);
}

// This test checks a registered lease can't be reused in SOLICT.
TEST_F(AllocEngine6Test, solicitReuseRegisteredLease6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    IOAddress addr("2001:db8:1::ad");

    // Create one subnet with a pool holding one address.
    initSubnet(IOAddress("2001:db8:1::"), addr, addr);

    // Initialize FQDN data for the lease.
    initFqdn("myhost.example.com", true, true);

    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_,
                               501, 502, subnet_->getID(), HWAddrPtr()));
    lease->state_ = Lease::STATE_REGISTERED;
    lease->cltt_ = time(0) - 200; // Registered 200 seconds ago
    lease->valid_lft_ = 400; // Lease was valid for 400 seconds
    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    // CASE 1: Asking for any address
    AllocEngine::ClientContext6 ctx1(subnet_, duid_, fqdn_fwd_, fqdn_rev_, hostname_,
                                     true, Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)));
    ctx1.currentIA().iaid_ = iaid_;

    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx1)));

    // Check that we did not get this single lease.
    EXPECT_FALSE(lease);

    // CASE 2: Asking specifically for this address
    AllocEngine::ClientContext6 ctx2(subnet_, duid_, false, false, "", true,
                                     Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
    ctx2.currentIA().iaid_ = iaid_;
    ctx2.currentIA().addHint(addr);

    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx2)));

    // Check that we did not get this single lease.
    EXPECT_FALSE(lease);
}

// This test checks if an expired lease can be reused in REQUEST (actual allocation)
TEST_F(AllocEngine6Test, requestReuseExpiredLease6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    IOAddress addr("2001:db8:1::ad");
    CfgMgr& cfg_mgr = CfgMgr::instance();
    cfg_mgr.clear(); // Get rid of the default test configuration

    // Create configuration similar to other tests, but with a single address pool
    subnet_ = Subnet6::create(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4, SubnetID(10));
    pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, addr, addr)); // just a single address
    subnet_->addPool(pool_);
    cfg_mgr.getStagingCfg()->getCfgSubnets6()->add(subnet_);
    cfg_mgr.commit();
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    // Let's create an expired lease
    DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
    const uint32_t other_iaid = 3568;

    const SubnetID other_subnetid = 999;
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, other_duid, other_iaid,
                               501, 502, other_subnetid, HWAddrPtr()));
    int64_t other_cumulative =
        getStatistics("cumulative-assigned-nas", other_subnetid);

    lease->cltt_ = time(0) - 500; // Allocated 500 seconds ago
    lease->valid_lft_ = 495; // Lease was valid for 495 seconds
    lease->fqdn_fwd_ = true;
    lease->fqdn_rev_ = true;
    lease->hostname_ = "myhost.example.com.";
    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    // A client comes along, asking specifically for this address
    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
                                    Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
    ctx.currentIA().iaid_ = iaid_;
    ctx.currentIA().addHint(addr);

    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));

    // Check that he got that single lease
    ASSERT_TRUE(lease);
    EXPECT_EQ(addr, lease->addr_);
    // This reactivated lease should have updated FQDN data.
    EXPECT_TRUE(lease->hostname_.empty());
    EXPECT_FALSE(lease->fqdn_fwd_);
    EXPECT_FALSE(lease->fqdn_rev_);

    // Check that the old lease has been returned.
    Lease6Ptr old_lease = expectOneLease(ctx.currentIA().old_leases_);
    ASSERT_TRUE(old_lease);

    // It should at least have the same IPv6 address.
    EXPECT_EQ(lease->addr_, old_lease->addr_);
    // Check that it carries not updated FQDN data.
    EXPECT_EQ("myhost.example.com.", old_lease->hostname_);
    EXPECT_TRUE(old_lease->fqdn_fwd_);
    EXPECT_TRUE(old_lease->fqdn_rev_);

    // Check that the lease is indeed updated in LeaseMgr
    Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
                                                               addr);
    ASSERT_TRUE(from_mgr);

    // Now check that the lease in LeaseMgr has the same parameters
    detailCompareLease(lease, from_mgr);

    // Verify the stats got adjusted correctly
    EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
    cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    glbl_cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
    EXPECT_TRUE(testStatistics("assigned-nas", -1, other_subnetid));
    EXPECT_FALSE(testStatistics("cumulative-assigned-nas",
                                other_cumulative, other_subnetid, false));
    EXPECT_TRUE(testStatistics("reclaimed-leases", 1));
    EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID()));
    EXPECT_TRUE(testStatistics("reclaimed-leases", 1, other_subnetid));
}

// This test checks checks an expired registered lease can't be reused in REQUEST.
TEST_F(AllocEngine6Test, requestReuseExpiredRegisteredLease6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    IOAddress addr("2001:db8:1::ad");
    CfgMgr& cfg_mgr = CfgMgr::instance();
    cfg_mgr.clear(); // Get rid of the default test configuration

    // Create configuration similar to other tests, but with a single address pool
    subnet_ = Subnet6::create(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4, SubnetID(10));
    pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, addr, addr)); // just a single address
    subnet_->addPool(pool_);
    cfg_mgr.getStagingCfg()->getCfgSubnets6()->add(subnet_);
    cfg_mgr.commit();

    // Let's create an expired registered lease
    DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
    const uint32_t other_iaid = 3568;

    const SubnetID other_subnetid = 999;
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, other_duid, other_iaid,
                               501, 502, other_subnetid, HWAddrPtr()));
    lease->state_ = Lease::STATE_REGISTERED;
    lease->cltt_ = time(0) - 500; // Registered 500 seconds ago
    lease->valid_lft_ = 495; // Lease was valid for 495 seconds
    lease->fqdn_fwd_ = true;
    lease->fqdn_rev_ = true;
    lease->hostname_ = "myhost.example.com.";
    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    // A client comes along, asking specifically for this address
    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
                                    Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
    ctx.currentIA().iaid_ = iaid_;
    ctx.currentIA().addHint(addr);

    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));

    // Check that we did not get this single lease.
    EXPECT_FALSE(lease);

    // Check that no old lease has been returned.
    Lease6Ptr old_lease = expectOneLease(ctx.currentIA().old_leases_);
    EXPECT_FALSE(old_lease);
}

// This test checks a registered lease can't be reused in REQUEST.
TEST_F(AllocEngine6Test, requestReuseRegisteredLease6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    IOAddress addr("2001:db8:1::ad");
    CfgMgr& cfg_mgr = CfgMgr::instance();
    cfg_mgr.clear(); // Get rid of the default test configuration

    // Create configuration similar to other tests, but with a single address pool
    subnet_ = Subnet6::create(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4, SubnetID(10));
    pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, addr, addr)); // just a single address
    subnet_->addPool(pool_);
    cfg_mgr.getStagingCfg()->getCfgSubnets6()->add(subnet_);
    cfg_mgr.commit();

    // Let's create a registered lease
    const SubnetID other_subnetid = 999;
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_,
                               501, 502, other_subnetid, HWAddrPtr()));
    lease->state_ = Lease::STATE_REGISTERED;
    lease->cltt_ = time(0) - 200; // Registered 200 seconds ago
    lease->valid_lft_ = 400; // Lease was valid for 400 seconds
    lease->fqdn_fwd_ = true;
    lease->fqdn_rev_ = true;
    lease->hostname_ = "myhost.example.com.";
    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    // A client comes along, asking specifically for this address
    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
                                    Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
    ctx.currentIA().iaid_ = iaid_;
    ctx.currentIA().addHint(addr);

    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));

    // Check that we did not get this single lease.
    EXPECT_FALSE(lease);

    // Check that no old lease has been returned.
    Lease6Ptr old_lease = expectOneLease(ctx.currentIA().old_leases_);
    EXPECT_FALSE(old_lease);
}

// This test checks if a released lease can be reused in REQUEST (actual allocation)
TEST_F(AllocEngine6Test, requestReuseReleasedLease6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    IOAddress addr("2001:db8:1::ad");
    CfgMgr& cfg_mgr = CfgMgr::instance();
    cfg_mgr.clear(); // Get rid of the default test configuration

    // Create configuration similar to other tests, but with a single address pool
    subnet_ = Subnet6::create(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4, SubnetID(10));
    pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, addr, addr)); // just a single address
    subnet_->addPool(pool_);
    cfg_mgr.getStagingCfg()->getCfgSubnets6()->add(subnet_);
    cfg_mgr.commit();
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    // Let's create a released lease
    DuidPtr other_duid = DuidPtr(new DUID(vector<uint8_t>(12, 0xff)));
    const uint32_t other_iaid = 3568;

    const SubnetID other_subnetid = 999;
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, other_duid, other_iaid,
                               501, 502, other_subnetid, HWAddrPtr()));
    lease->state_ = Lease6::STATE_RELEASED;
    int64_t other_cumulative =
        getStatistics("cumulative-assigned-nas", other_subnetid);

    lease->cltt_ = time(0) - 500; // Allocated 500 seconds ago
    lease->valid_lft_ = 495; // Lease was valid for 495 seconds
    lease->fqdn_fwd_ = true;
    lease->fqdn_rev_ = true;
    lease->hostname_ = "myhost.example.com.";
    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    // A client comes along, asking specifically for this address
    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
                                    Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
    ctx.currentIA().iaid_ = iaid_;
    ctx.currentIA().addHint(addr);

    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));

    // Check that he got that single lease
    ASSERT_TRUE(lease);
    EXPECT_EQ(addr, lease->addr_);
    // This reactivated lease should have updated FQDN data.
    EXPECT_TRUE(lease->hostname_.empty());
    EXPECT_FALSE(lease->fqdn_fwd_);
    EXPECT_FALSE(lease->fqdn_rev_);
    EXPECT_EQ(Lease6::STATE_DEFAULT, lease->state_);

    // Check that the old lease has been returned.
    Lease6Ptr old_lease = expectOneLease(ctx.currentIA().old_leases_);
    ASSERT_TRUE(old_lease);

    // It should at least have the same IPv6 address.
    EXPECT_EQ(lease->addr_, old_lease->addr_);
    // Check that it carries not updated FQDN data.
    EXPECT_EQ("myhost.example.com.", old_lease->hostname_);
    EXPECT_TRUE(old_lease->fqdn_fwd_);
    EXPECT_TRUE(old_lease->fqdn_rev_);
    EXPECT_EQ(Lease6::STATE_RELEASED, old_lease->state_);

    // Check that the lease is indeed updated in LeaseMgr
    Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
                                                               addr);
    ASSERT_TRUE(from_mgr);

    EXPECT_EQ(Lease6::STATE_DEFAULT, from_mgr->state_);

    // Now check that the lease in LeaseMgr has the same parameters
    detailCompareLease(lease, from_mgr);

    // Verify the stats got adjusted correctly
    EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
    cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    glbl_cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
    EXPECT_FALSE(testStatistics("cumulative-assigned-nas",
                                other_cumulative, other_subnetid, false));
    EXPECT_TRUE(testStatistics("reclaimed-leases", 1));
    EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID()));
    EXPECT_TRUE(testStatistics("reclaimed-leases", 1, other_subnetid));
}

// Checks if the lease lifetime is extended when the client sends the
// Request.
TEST_F(AllocEngine6Test, requestExtendLeaseLifetime) {
    // Create a lease for the client.
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::15"),
                               duid_, iaid_, 300, 400,
                               subnet_->getID(), HWAddrPtr()));

    // Allocated 200 seconds ago - half of the lifetime.
    time_t lease_cltt = time(0) - 200;
    lease->cltt_ = lease_cltt;

    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    // Client should receive a lease.
    Lease6Ptr new_lease = simpleAlloc6Test(pool_, IOAddress("::"), false);
    ASSERT_TRUE(new_lease);

    // And the lease lifetime should be extended.
    EXPECT_GT(new_lease->cltt_, lease_cltt)
        << "Lease lifetime was not extended, but it should";
}

// Checks if the lease lifetime is extended when the client sends the
// Request and the client has a reservation for the lease.
TEST_F(AllocEngine6Test, requestExtendLeaseLifetimeForReservation) {
    // Create reservation for the client. This is in-pool reservation,
    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);

    // Create a lease for the client.
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1c"),
                               duid_, iaid_, 300, 400,
                               subnet_->getID(), HWAddrPtr()));

    // Allocated 200 seconds ago - half of the lifetime.
    time_t lease_cltt = time(0) - 200;
    lease->cltt_ = lease_cltt;

    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    // Client should receive a lease.
    Lease6Ptr new_lease = simpleAlloc6Test(pool_, IOAddress("::"), false);
    ASSERT_TRUE(new_lease);

    // And the lease lifetime should be extended.
    EXPECT_GT(new_lease->cltt_, lease_cltt)
        << "Lease lifetime was not extended, but it should";
}

// Checks if the lease lifetime is extended when the client sends the
// Renew.
TEST_F(AllocEngine6Test, renewExtendLeaseLifetime) {
    // Create a lease for the client.
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::15"),
                               duid_, iaid_, 300, 400,
                               subnet_->getID(), HWAddrPtr()));

    // Allocated 200 seconds ago - half of the lifetime.
    time_t lease_cltt = time(0) - 200;
    lease->cltt_ = lease_cltt;

    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    AllocEngine engine(100);

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::15"), 128));

    // Client should receive a lease.
    Lease6Collection renewed = renewTest(engine, pool_, hints, IN_SUBNET, IN_POOL);
    ASSERT_EQ(1, renewed.size());

    // And the lease lifetime should be extended.
    EXPECT_GT(renewed[0]->cltt_, lease_cltt)
        << "Lease lifetime was not extended, but it should";
}

// Checks a registered lease can't be renewed.
TEST_F(AllocEngine6Test, renewRegisteredLease6) {
    // Create a registered lease for the client.
    IOAddress addr("2001:db8:1::15");
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr,
                               duid_, iaid_, 300, 400,
                               subnet_->getID(), HWAddrPtr()));

    lease->state_ = Lease::STATE_REGISTERED;
    // Allocated 200 seconds ago - half of the lifetime.
    time_t lease_cltt = time(0) - 200;
    lease->cltt_ = lease_cltt;

    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    AllocEngine engine(100);

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(addr, 128));

    // Get renewed leases.
    Lease6Collection renewed = renewTest(engine, pool_, hints, IN_SUBNET, IN_POOL);
    for (auto const& l : renewed) {
        // Not registered.
        EXPECT_EQ(0, l->state_);
        // Another address.
        EXPECT_NE(addr, l->addr_);
    }
}

// Checks that a renewed lease uses default lifetimes.
TEST_F(AllocEngine6Test, defaultRenewLeaseLifetime) {
    // Create a lease for the client.
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::15"),
                               duid_, iaid_, 300, 400,
                               subnet_->getID(), HWAddrPtr()));

    // Allocated 200 seconds ago - half of the lifetime.
    time_t lease_cltt = time(0) - 200;
    lease->cltt_ = lease_cltt;

    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    AllocEngine engine(100);
    subnet_->setPreferred(Triplet<uint32_t>(200, 300, 400));
    subnet_->setValid(Triplet<uint32_t>(300, 400, 500));

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::15"),
                                          128, 0, 0));

    // Client should receive a lease.
    Lease6Collection renewed = renewTest(engine, pool_, hints, IN_SUBNET, IN_POOL);
    ASSERT_EQ(1, renewed.size());

    // And the lease lifetime should be extended.
    EXPECT_GT(renewed[0]->cltt_, lease_cltt)
        << "Lease lifetime was not extended, but it should";

    // Checks that default values are used for lifetimes.
    EXPECT_EQ(300, renewed[0]->preferred_lft_);
    EXPECT_EQ(400, renewed[0]->valid_lft_);
}

// Checks that a renewed lease uses specified lifetimes.
TEST_F(AllocEngine6Test, hintRenewLeaseLifetime) {
    // Create a lease for the client.
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::15"),
                               duid_, iaid_, 300, 400,
                               subnet_->getID(), HWAddrPtr()));

    // Allocated 200 seconds ago - half of the lifetime.
    time_t lease_cltt = time(0) - 200;
    lease->cltt_ = lease_cltt;

    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    AllocEngine engine(100);
    subnet_->setPreferred(Triplet<uint32_t>(200, 300, 400));
    subnet_->setValid(Triplet<uint32_t>(300, 400, 500));

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::15"),
                                          128, 301, 399));

    // Client should receive a lease.
    Lease6Collection renewed = renewTest(engine, pool_, hints, IN_SUBNET, IN_POOL);
    ASSERT_EQ(1, renewed.size());

    // And the lease lifetime should be extended.
    EXPECT_GT(renewed[0]->cltt_, lease_cltt)
        << "Lease lifetime was not extended, but it should";

    // Checks that specified values are used for lifetimes.
    EXPECT_EQ(301, renewed[0]->preferred_lft_);
    EXPECT_EQ(399, renewed[0]->valid_lft_);
}

// Checks that a renewed lease uses min lifetimes.
TEST_F(AllocEngine6Test, minRenewLeaseLifetime) {
    // Create a lease for the client.
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::15"),
                               duid_, iaid_, 300, 400,
                               subnet_->getID(), HWAddrPtr()));

    // Allocated 200 seconds ago - half of the lifetime.
    time_t lease_cltt = time(0) - 200;
    lease->cltt_ = lease_cltt;

    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    AllocEngine engine(100);
    subnet_->setPreferred(Triplet<uint32_t>(200, 300, 400));
    subnet_->setValid(Triplet<uint32_t>(300, 400, 500));

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::15"),
                                          128, 100, 200));

    // Client should receive a lease.
    Lease6Collection renewed = renewTest(engine, pool_, hints, IN_SUBNET, IN_POOL);
    ASSERT_EQ(1, renewed.size());

    // And the lease lifetime should be extended.
    EXPECT_GT(renewed[0]->cltt_, lease_cltt)
        << "Lease lifetime was not extended, but it should";

    // Checks that min values are used for lifetimes.
    EXPECT_EQ(200, renewed[0]->preferred_lft_);
    EXPECT_EQ(300, renewed[0]->valid_lft_);
}

// Checks that a renewed lease uses max lifetimes.
TEST_F(AllocEngine6Test, maxRenewLeaseLifetime) {
    // Create a lease for the client.
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::15"),
                               duid_, iaid_, 300, 400,
                               subnet_->getID(), HWAddrPtr()));

    // Allocated 200 seconds ago - half of the lifetime.
    time_t lease_cltt = time(0) - 200;
    lease->cltt_ = lease_cltt;

    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    AllocEngine engine(100);
    subnet_->setPreferred(Triplet<uint32_t>(200, 300, 400));
    subnet_->setValid(Triplet<uint32_t>(300, 400, 500));

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::15"),
                                          128, 500, 600));

    // Client should receive a lease.
    Lease6Collection renewed = renewTest(engine, pool_, hints, IN_SUBNET, IN_POOL);
    ASSERT_EQ(1, renewed.size());

    // And the lease lifetime should be extended.
    EXPECT_GT(renewed[0]->cltt_, lease_cltt)
        << "Lease lifetime was not extended, but it should";

    // Checks that max values are used for lifetimes.
    EXPECT_EQ(400, renewed[0]->preferred_lft_);
    EXPECT_EQ(500, renewed[0]->valid_lft_);
}

// Verifies renewal uses class life times via getLifetimes() in invoked by
// extendLease6().
TEST_F(AllocEngine6Test, renewClassLeaseLifetime) {
    // Create a lease for the client.
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::15"),
                               duid_, iaid_, 300, 400,
                               subnet_->getID(), HWAddrPtr()));
    // Allocated 200 seconds ago - half of the lifetime.
    time_t lease_cltt = time(0) - 200;
    lease->cltt_ = lease_cltt;

    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    AllocEngine engine(100);

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::15"), 128));

    Lease::Type type = Lease::TYPE_NA;

    Pkt6Ptr query(new Pkt6(DHCPV6_RENEW, 1234));
    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "",
                                    false, query);
    ctx.currentIA().hints_ = hints;
    ctx.currentIA().iaid_ = iaid_;
    ctx.currentIA().type_ = type;

    // Add a client class with both valid and preferred lifetime values.
    ClientClassDefPtr class_def(new ClientClassDef("lft_class", ExpressionPtr()));
    Triplet<uint32_t> valid_lft(600, 700, 800);
    class_def->setValid(valid_lft);
    Triplet<uint32_t> preferred_lft(650, 750, 850);
    class_def->setPreferred(preferred_lft);
    CfgMgr::instance().getCurrentCfg()->getClientClassDictionary()->addClass(class_def);
    ctx.query_->addClass(class_def->getName());


    findReservation(engine, ctx);
    Lease6Collection renewed = engine.renewLeases6(ctx);
    ASSERT_EQ(1, renewed.size());

    // And the lease lifetime should be extended.
    EXPECT_GT(renewed[0]->cltt_, lease_cltt)
        << "Lease lifetime was not extended, but it should";

    // Verify life times came from the class. Preferred should get adjusted.
    EXPECT_EQ(renewed[0]->preferred_lft_, 437);
    EXPECT_EQ(renewed[0]->valid_lft_, 700);
}

// Checks if a released lease is renewed and that its state is set to default.
TEST_F(AllocEngine6Test, renewReleasedLease) {
    // Create a released lease for the client.
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::15"),
                               duid_, iaid_, 300, 400,
                               subnet_->getID(), HWAddrPtr()));
    lease->state_ = Lease6::STATE_RELEASED;

    // Allocated 200 seconds ago - half of the lifetime.
    time_t lease_cltt = time(0) - 200;
    lease->cltt_ = lease_cltt;

    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    AllocEngine engine(100);

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::15"), 128));

    // Client should renew the lease.
    Lease6Collection renewed = renewTest(engine, pool_, hints, IN_SUBNET, IN_POOL);
    ASSERT_EQ(1, renewed.size());

    // And the lease lifetime should be extended.
    EXPECT_GT(renewed[0]->cltt_, lease_cltt)
        << "Lease lifetime was not extended, but it should";

    // The lease should have the default state.
    EXPECT_EQ(Lease6::STATE_DEFAULT, renewed[0]->state_);
}

// Checks if a released lease is re-allocated and that its state set to default.
TEST_F(AllocEngine6Test, reallocReleasedLease) {
    // Create a released lease for the client.
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::15"),
                               duid_, iaid_, 300, 400,
                               subnet_->getID(), HWAddrPtr()));
    lease->state_ = Lease6::STATE_RELEASED;

    // Allocated 200 seconds ago - half of the lifetime.
    time_t lease_cltt = time(0) - 200;
    lease->cltt_ = lease_cltt;

    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    AllocEngine engine(100);

    // Reallocate the released lease.
    Lease6Ptr renewed = simpleAlloc6Test(pool_, IOAddress("::"), false);
    ASSERT_TRUE(renewed);

    // And the lease lifetime should be extended.
    EXPECT_GT(renewed->cltt_, lease_cltt)
        << "Lease lifetime was not extended, but it should";

    // The lease should have the default state.
    EXPECT_EQ(Lease6::STATE_DEFAULT, renewed->state_);
}

// Renew and the client has a reservation for the lease.
TEST_F(AllocEngine6Test, renewExtendLeaseLifetimeForReservation) {
    // Create reservation for the client. This is in-pool reservation,
    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::15"), 128);

    // Create a lease for the client.
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::15"),
                               duid_, iaid_, 300, 400,
                               subnet_->getID(), HWAddrPtr()));

    // Allocated 200 seconds ago - half of the lifetime.
    time_t lease_cltt = time(0) - 200;
    lease->cltt_ = lease_cltt;

    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    AllocEngine engine(100);

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::15"), 128));

    // Client should receive a lease.
    Lease6Collection renewed = renewTest(engine, pool_, hints, IN_SUBNET, IN_POOL);
    ASSERT_EQ(1, renewed.size());

    // And the lease lifetime should be extended.
    EXPECT_GT(renewed[0]->cltt_, lease_cltt)
        << "Lease lifetime was not extended, but it should";
}

// --- v6 host reservation ---

// Checks that a client gets the address reserved (in-pool case)
// This test checks the behavior of the allocation engine in the following
// scenario:
// - Client has no lease in the database.
// - Client has an in-pool reservation.
// - Client sends SOLICIT without any hints.
// - Client is allocated a reserved address.
//
// Note that a DHCPv6 client can, but doesn't have to send any hints in its
// Solicit message.
TEST_F(AllocEngine6Test, reservedAddressInPoolSolicitNoHint) {
    // Create reservation for the client. This is in-pool reservation,
    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);

    AllocEngine engine(100);
}

// Checks that a client gets the address reserved (in-pool case)
// This test checks the behavior of the allocation engine in the following
// scenario:
// - Client has no lease in the database.
// - Client has an in-pool reservation.
// - Client sends REQUEST without any hints.
// - Client is allocated a reserved address.
//
// Note that DHCPv6 client must send an address in REQUEST that the server
// offered in Advertise. Nevertheless, the client may ignore this requirement.
TEST_F(AllocEngine6Test, reservedAddressInPoolRequestNoHint) {
    // Create reservation for the client. This is in-pool reservation,
    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);

    AllocEngine engine(100);

    // Assigned count should be zero.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));

    // Get the cumulative count of assigned addresses.
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("::"), false);
    ASSERT_TRUE(lease);
    EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());

    // Assigned count should have been incremented by one.
    EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
    cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    glbl_cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
}

// Checks that a client gets the address reserved (in-pool case)
// This test checks the behavior of the allocation engine in the following
// scenario:
// - Client has no lease in the database.
// - Client has an in-pool reservation.
// - Client sends SOLICIT with a hint that does not match reservation
// - Client is allocated a reserved address, not the hint.
//
// Note that DHCPv6 client can, but don't have to send any hints in its
// Solicit message.
TEST_F(AllocEngine6Test, reservedAddressInPoolSolicitValidHint) {
    // Create reservation for the client. This is in-pool reservation,
    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);

    AllocEngine engine(100);

    // Assigned count should be zero.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));

    // Get the cumulative count of assigned addresses.
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    // Let's pretend the client sends hint 2001:db8:1::10.
    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::10"), true);
    ASSERT_TRUE(lease);

    // The hint should be ignored and the reserved address should be assigned
    EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());

    // Assigned count should not have been incremented.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
}

// Checks that a client gets the address reserved (in-pool case)
// This test checks the behavior of the allocation engine in the following
// scenario:
// - Client has no lease in the database.
// - Client has an in-pool reservation.
// - Client sends REQUEST with a hint that does not match reservation
// - Client is allocated a reserved address, not the hint.
//
// Note that DHCPv6 client must send an address in REQUEST that the server
// offered in Advertise. Nevertheless, the client may ignore this requirement.
TEST_F(AllocEngine6Test, reservedAddressInPoolRequestValidHint) {
    // Create reservation for the client This is in-pool reservation,
    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);

    AllocEngine engine(100);

    // Assigned count should be zero.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));

    // Get the cumulative count of assigned addresses.
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    // Let's pretend the client sends hint 2001:db8:1::10.
    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::10"), false);
    ASSERT_TRUE(lease);

    // The hint should be ignored and the reserved address should be assigned
    EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());

    // Assigned count should have been incremented.
    EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
    cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    glbl_cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
}

// Checks that a client gets the address reserved (in-pool case)
// This test checks the behavior of the allocation engine in the following
// scenario:
// - Client has no lease in the database.
// - Client has an in-pool reservation.
// - Client sends SOLICIT with a hint that does matches reservation
// - Client is allocated a reserved address, not the hint.
//
// Note that DHCPv6 client can, but don't have to send any hints in its
// Solicit message.
TEST_F(AllocEngine6Test, reservedAddressInPoolSolicitMatchingHint) {
    // Create reservation for the client. This is in-pool reservation,
    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);

    AllocEngine engine(100);

    // Assigned count should be zero.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));

    // Get the cumulative count of assigned addresses.
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    // Let's pretend the client sends hint 2001:db8:1::10.
    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::1c"), true);
    ASSERT_TRUE(lease);

    // The hint should be ignored and the reserved address should be assigned
    EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());

    // Assigned count should not have been incremented.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
}

// Checks that a client gets the address reserved (in-pool case)
// This test checks the behavior of the allocation engine in the following
// scenario:
// - Client has no lease in the database.
// - Client has an in-pool reservation.
// - Client sends REQUEST with a hint that does not match reservation
// - Client is allocated a reserved address, not the hint.
//
// Note that DHCPv6 client must send an address in REQUEST that the server
// offered in Advertise. Nevertheless, the client may ignore this requirement.
TEST_F(AllocEngine6Test, reservedAddressInPoolRequestMatchingHint) {
    // Create reservation for the client. This is in-pool reservation,
    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);

    AllocEngine engine(100);

    // Assigned count should be zero.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));

    // Get the cumulative count of assigned addresses.
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    // Let's pretend the client sends hint 2001:db8:1::10.
    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::1c"), false);
    ASSERT_TRUE(lease);

    // The hint should be ignored and the reserved address should be assigned
    EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());

    // Assigned count should have been incremented.
    EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
    cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    glbl_cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
}

// Checks that a client gets the address reserved (out-of-pool case)
// This test checks the behavior of the allocation engine in the following
// scenario:
// - Client has no lease in the database.
// - Client has an out-of-pool reservation.
// - Client sends SOLICIT without any hints.
// - Client is allocated a reserved address.
//
// Note that DHCPv6 client can, but don't have to send any hints in its
// Solicit message.
TEST_F(AllocEngine6Test, reservedAddressOutOfPoolSolicitNoHint) {
    // Create reservation for the client. This is out-of-pool reservation,
    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8::abcd"), 128);

    AllocEngine engine(100);

    // Assigned count should be zero.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));

    // Get the cumulative count of assigned addresses.
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("::"), true, false);
    ASSERT_TRUE(lease);
    EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());

    // Assigned count should not have been incremented.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
}

// Checks that a client gets the address reserved (out-of-pool case)
// This test checks the behavior of the allocation engine in the following
// scenario:
// - Client has no lease in the database.
// - Client has an out-of-pool reservation.
// - Client sends REQUEST without any hints.
// - Client is allocated a reserved address.
//
// Note that DHCPv6 client must send an address in REQUEST that the server
// offered in Advertise. Nevertheless, the client may ignore this requirement.
TEST_F(AllocEngine6Test, reservedAddressOutOfPoolRequestNoHint) {
    // Create reservation for the client. This is out-of-pool reservation,
    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8::abcd"), 128);

    AllocEngine engine(100);

    // Assigned count should be zero.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));

    // Get the cumulative count of assigned addresses.
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("::"), false, false);
    ASSERT_TRUE(lease);
    EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());

    // We should not have bumped the address counter
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
}

// Checks that a client gets the address reserved (in-pool case)
// This test checks the behavior of the allocation engine in the following
// scenario:
// - Client has no lease in the database.
// - Client has an in-pool reservation.
// - Client sends SOLICIT with a hint that does not match reservation
// - Client is allocated a reserved address, not the hint.
//
// Note that DHCPv6 client can, but don't have to send any hints in its
// Solicit message.
TEST_F(AllocEngine6Test, reservedAddressOutOfPoolSolicitValidHint) {
    // Create reservation for the client. This is out-of-pool reservation,
    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8::abcd"), 128);

    AllocEngine engine(100);

    // Assigned count should be zero.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));

    // Get the cumulative count of assigned addresses.
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    // Let's pretend the client sends hint 2001:db8:1::10.
    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::10"), true, false);
    ASSERT_TRUE(lease);

    // The hint should be ignored and the reserved address should be assigned
    EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());

    // We should not have bumped the address counter
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
}

// Checks that a client gets the address reserved (out-of-pool case)
// This test checks the behavior of the allocation engine in the following
// scenario:
// - Client has no lease in the database.
// - Client has an out-of-pool reservation.
// - Client sends REQUEST with a hint that does not match reservation
// - Client is allocated a reserved address, not the hint.
//
// Note that DHCPv6 client must send an address in REQUEST that the server
// offered in Advertise. Nevertheless, the client may ignore this requirement.
TEST_F(AllocEngine6Test, reservedAddressOutOfPoolRequestValidHint) {
    // Create reservation for the client. This is out-of-pool reservation,
    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8::abcd"), 128);

    AllocEngine engine(100);

    // Assigned count should be zero.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));

    // Get the cumulative count of assigned addresses.
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    // Let's pretend the client sends hint 2001:db8:1::10.
    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::10"), false, false);
    ASSERT_TRUE(lease);

    // The hint should be ignored and the reserved address should be assigned
    EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());

    // We should not have bumped the address counter
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
}

// Checks that a client gets the address reserved (out-of-pool case)
// This test checks the behavior of the allocation engine in the following
// scenario:
// - Client has no lease in the database.
// - Client has an out-of-pool reservation.
// - Client sends SOLICIT with a hint that does matches reservation
// - Client is allocated a reserved address, not the hint.
//
// Note that DHCPv6 client can, but don't have to send any hints in its
// Solicit message.
TEST_F(AllocEngine6Test, reservedAddressOutOfPoolSolicitMatchingHint) {
    // Create reservation for the client. This is out-of-pool reservation,
    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8::abcd"), 128);

    AllocEngine engine(100);

    // Assigned count should be zero.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));

    // Get the cumulative count of assigned addresses.
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    // Let's pretend the client sends hint 2001:db8:1::10.
    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::1c"), true, false);
    ASSERT_TRUE(lease);

    // The hint should be ignored and the reserved address should be assigned
    EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());

    // We should not have bumped the address counter
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
}

// Checks that a client gets the address reserved (out-of-pool case)
// This test checks the behavior of the allocation engine in the following
// scenario:
// - Client has no lease in the database.
// - Client has an out-of-pool reservation.
// - Client sends REQUEST with a hint that does not match reservation
// - Client is allocated a reserved address, not the hint.
//
// Note that DHCPv6 client must send an address in REQUEST that the server
// offered in Advertise. Nevertheless, the client may ignore this requirement.
TEST_F(AllocEngine6Test, reservedAddressOutOfPoolRequestMatchingHint) {
    // Create reservation for the client. This is out-of-pool reservation,
    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8::abcd"), 128);

    AllocEngine engine(100);

    // Assigned count should be zero.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));

    // Get the cumulative count of assigned addresses.
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    // Let's pretend the client sends hint 2001:db8:1::10.
    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::1c"), false, false);
    ASSERT_TRUE(lease);

    // The hint should be ignored and the reserved address should be assigned
    EXPECT_EQ("2001:db8::abcd", lease->addr_.toText());

    // We should not have bumped the address counter
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
}

// In the following situation:
// - client is assigned an address A
// - HR is made for *this* client to get B
// - client tries to get address A:
//    Check that his existing lease for lease A is removed
//    Check that he is assigned a new lease for B
// - verify that the number of assigned address behaves as expected
TEST_F(AllocEngine6Test, reservedAddressInPoolReassignedThis) {
    AllocEngine engine(100);

    // Assigned count should be zero.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));

    // Get the cumulative count of assigned addresses.
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    // Client gets an address
    Lease6Ptr lease1 = simpleAlloc6Test(pool_, IOAddress("::"), false);
    ASSERT_TRUE(lease1);

    // We should have bumped the address counter
    EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
    cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    glbl_cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));

    // Just check that if the client requests again, it will get the same
    // address.
    Lease6Ptr lease2 = simpleAlloc6Test(pool_, lease1->addr_, false);
    ASSERT_TRUE(lease2);
    detailCompareLease(lease1, lease2);

    // We should not have bumped the address counter again
    EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));

    // Now admin creates a reservation for this client. This is in-pool
    // reservation, as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);

    // Just check that this time the client will get.
    Lease6Ptr lease3 = simpleAlloc6Test(pool_, lease1->addr_, false);
    ASSERT_TRUE(lease3);

    // Check that previous lease was not used anymore.
    EXPECT_NE(lease1->addr_.toText(), lease3->addr_.toText());

    // Check that the reserved address was indeed assigned.
    EXPECT_EQ("2001:db8:1::1c", lease3->addr_.toText());

    // Check that the old lease is gone.
    Lease6Ptr old = LeaseMgrFactory::instance().getLease6(lease1->type_,
                                                          lease1->addr_);
    EXPECT_FALSE(old);

    // Check that the reserved lease is in the database.
    Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease1->type_,
                                                  IOAddress("2001:db8:1::1c"));

    ASSERT_TRUE(from_mgr);

    // Now check that the lease in LeaseMgr has the same parameters
    detailCompareLease(lease3, from_mgr);

    // Lastly check to see that the address counter is still 1, we should have
    // have decremented it on the implied release and incremented it on the reserved
    EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
    cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    glbl_cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
}

// In the following situation:
// - client X is assigned an address A
// - HR is made for client Y (*other* client) to get A
// - client X tries to get address A:
//    Check that his existing lease for lease A is removed
//    Check that he is assigned a new lease
TEST_F(AllocEngine6Test, reservedAddressInPoolReassignedOther) {
    AllocEngine engine(100);

    // Assigned count should be zero.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));

    // Get the cumulative count of assigned addresses.
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    // Client gets an address
    Lease6Ptr lease1 = simpleAlloc6Test(pool_, IOAddress("::"), false);
    ASSERT_TRUE(lease1);

    // We should have bumped the address counter
    EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
    cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    glbl_cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));

    // Just check that if the client requests again, it will get the same
    // address.
    Lease6Ptr lease2 = simpleAlloc6Test(pool_, lease1->addr_, false);
    ASSERT_TRUE(lease2);
    detailCompareLease(lease1, lease2);

    // We should not have bumped the address counter again
    EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));

    // Now admin creates a reservation for this client. Let's use the
    // address client X just received. Let's generate a host, but don't add it
    // to the HostMgr yet.
    HostPtr host = createHost6(false, IPv6Resrv::TYPE_NA, lease1->addr_, 128);

    // We need to tweak reservation id: use a different DUID for client Y
    vector<uint8_t> other_duid(8, 0x45);
    host->setIdentifier(&other_duid[0], other_duid.size(), Host::IDENT_DUID);

    // Ok, now add it to the HostMgr
    addHost(host);

    // Just check that this time the client will get a different lease.
    Lease6Ptr lease3 = simpleAlloc6Test(pool_, lease1->addr_, false);
    ASSERT_TRUE(lease3);

    // Check that previous lease was not used anymore.
    EXPECT_NE(lease1->addr_.toText(), lease3->addr_.toText());

    // Check that the old lease is gone.
    Lease6Ptr old = LeaseMgrFactory::instance().getLease6(lease1->type_,
                                                          lease1->addr_);
    EXPECT_FALSE(old);

    // Check that the reserved lease is in the database.
    Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease1->type_,
                                                               lease3->addr_);

    ASSERT_TRUE(from_mgr);

    // Now check that the lease in LeaseMgr has the same parameters
    detailCompareLease(lease3, from_mgr);

    // Lastly check to see that the address counter is still 1 we should have
    // have decremented it on the implied release and incremented it on the reserved
    EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
    cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    glbl_cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
}

// Checks that a reserved address for client A is not assigned when
// other clients are requesting addresses. The scenario is as follows:
// we use a regular pool with 17 addresses in it. One of them is
// reserved for client A. Now we try to allocate addresses for 30 clients
// (A is not one of them). The first 16 attempts should succeed. Then
// we run out of addresses and remaining 14 clients will get nothing.
// Finally, we check that client A still can get his reserved address.
TEST_F(AllocEngine6Test, reservedAddress) {
    AllocEngine engine(100);

    // Create reservation for the client. This is in-pool reservation,
    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::12"), 128);

    // Let's generate 30 DUIDs, each of them 16 bytes long
    vector<DuidPtr> clients;
    for (int i = 0; i < 30; i++) {
        vector<uint8_t> data(16, i);
        clients.push_back(DuidPtr(new DUID(data)));
    }

    // The default pool is 2001:db8:1::10 to 2001:db8:1::20. There's 17
    // addresses in it. One of them is reserved, so this means that we should
    // get 16 successes and 14 (30-16) failures.
    int success = 0;
    int failure = 0;
    for (int i = 0; i < 30; i++) {
        Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
        AllocEngine::ClientContext6 ctx(subnet_, clients[i], false, false, "",
                                        false, query);
        ctx.currentIA().iaid_ = iaid_;

        findReservation(engine, ctx);
        Lease6Collection leases = engine.allocateLeases6(ctx);
        if (leases.empty()) {
            failure++;
            std::cout << "Alloc for client " << (int)i << " failed." << std::endl;
            EXPECT_EQ(failure, getStatistics("v6-allocation-fail"));
            EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network"));
            EXPECT_EQ(failure, getStatistics("v6-allocation-fail-subnet"));
            EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools"));
            EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes"));

            EXPECT_EQ(failure, getStatistics("v6-allocation-fail", subnet_->getID()));
            EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network", subnet_->getID()));
            EXPECT_EQ(failure, getStatistics("v6-allocation-fail-subnet", subnet_->getID()));
            EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", subnet_->getID()));
            EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", subnet_->getID()));
        } else {
            success++;
            std::cout << "Alloc for client " << (int)i << " succeeded:"
                      << leases[0]->addr_.toText() << std::endl;

            // The assigned addresses must not match the one reserved.
            EXPECT_NE("2001:db8:1::12", leases[0]->addr_.toText());
        }
    }

    EXPECT_EQ(16, success);
    EXPECT_EQ(14, failure);

    // We're now pretty sure that any clients other than the reserved address
    // will not get any service. Now let's check if the client that has the
    // address reserved, will get it (despite the pool being depleted).
    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
                                    Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
    ctx.currentIA().iaid_ = iaid_;

    findReservation(engine, ctx);
    Lease6Collection leases = engine.allocateLeases6(ctx);
    ASSERT_EQ(1, leases.size());
    EXPECT_EQ("2001:db8:1::12", leases[0]->addr_.toText());
}

// Checks if the allocateLeases throws exceptions for invalid input data.
TEST_F(AllocEngine6Test, allocateLeasesInvalidData) {
    AllocEngine engine(100);

    // That looks like a valid context.
    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
                                    Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
    ctx.currentIA().iaid_ = iaid_;

    Lease6Collection leases;

    // Let's break it!
    ctx.subnet_.reset();

    // Subnet is required for allocation, so we should get no leases.
    EXPECT_NO_THROW(leases = engine.allocateLeases6(ctx));
    ASSERT_TRUE(leases.empty());

    EXPECT_EQ(0, getStatistics("v6-allocation-fail"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes"));

    EXPECT_EQ(0, getStatistics("v6-allocation-fail", subnet_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network", subnet_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet", subnet_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", subnet_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", subnet_->getID()));

    // Let's fix this and break it in a different way.
    ctx.subnet_ = subnet_;
    ctx.duid_.reset();

    // We must know who we're allocating for. No duid = no service.
    EXPECT_NO_THROW(leases = engine.allocateLeases6(ctx));
    ASSERT_TRUE(leases.empty());

    EXPECT_EQ(0, getStatistics("v6-allocation-fail"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes"));

    EXPECT_EQ(0, getStatistics("v6-allocation-fail", subnet_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network", subnet_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet", subnet_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", subnet_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", subnet_->getID()));
}

// Checks whether an address can be renewed (simple case, no reservation tricks)
TEST_F(AllocEngine6Test, addressRenewal) {
    AllocEngine engine(100);

    // Assigned count should zero.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));

    // Get the cumulative count of assigned addresses.
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    Lease6Collection leases;

    leases = allocateTest(engine, pool_, IOAddress("::"), false, true);
    ASSERT_EQ(1, leases.size());

    // Assigned count should be one.
    EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
    cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    glbl_cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(leases[0]->addr_, 128));

    Lease6Collection renewed = renewTest(engine, pool_, hints, IN_SUBNET, IN_POOL);
    ASSERT_EQ(1, renewed.size());

    // Check that the lease was indeed renewed and hasn't changed
    // (i.e. the same address, preferred and valid lifetimes)

    /// @todo: use leaseCompare, but ignore cltt_
    EXPECT_EQ(leases[0]->addr_, renewed[0]->addr_);
    EXPECT_EQ(leases[0]->type_, renewed[0]->type_);
    EXPECT_EQ(leases[0]->preferred_lft_, renewed[0]->preferred_lft_);
    EXPECT_EQ(leases[0]->valid_lft_, renewed[0]->valid_lft_);

    // Assigned count should still be one.
    EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
}

// Checks whether an address can be renewed (in-pool reservation)
TEST_F(AllocEngine6Test, reservedAddressRenewal) {
    // Create reservation for the client. This is in-pool reservation,
    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);

    AllocEngine engine(100);

    // Assigned count should zero.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));

    // Get the cumulative count of assigned addresses.
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    Lease6Collection leases;

    leases = allocateTest(engine, pool_, IOAddress("::"), false, true);
    ASSERT_EQ(1, leases.size());
    ASSERT_EQ("2001:db8:1::1c", leases[0]->addr_.toText());

    // Assigned count should be one.
    EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
    cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    glbl_cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(leases[0]->addr_, 128));

    Lease6Collection renewed = renewTest(engine, pool_, hints, IN_SUBNET, IN_POOL);
    ASSERT_EQ(1, renewed.size());
    ASSERT_EQ("2001:db8:1::1c", leases[0]->addr_.toText());

    // Assigned count should still be one.
    EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
}

// Checks whether a single host can have more than one reservation.
//
/// @todo: as of #3677, this does not work. When processing solicit with two
/// IA_NAs and two reservations, there currently no way to indicate that
/// the first reservation should be used for the first IA and the second
/// reservation for the second IA. This works for Requests and Renews, though.
/// In both of those messages, when processing of the first IA is complete,
/// we have a lease in the database. Based on that, when processing the second
/// IA we can detect that the first reserved address is in use already and
/// use the second reservation.
TEST_F(AllocEngine6Test, DISABLED_reserved2AddressesSolicit) {
    // Create reservation for the client. This is in-pool reservation,
    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    // Two addresses are reserved: 2001:db8:1::babe and 2001:db8:1::cafe
    HostPtr host = createHost6(true, IPv6Resrv::TYPE_NA,
                               IOAddress("2001:db8:1::babe"), 128);

    IPv6Resrv resv2(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::cafe"), 128);
    host->addReservation(resv2);
    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
    CfgMgr::instance().commit();

    AllocEngine engine(100);

    AllocEngine::ClientContext6 ctx1(subnet_, duid_, false, false, "", true,
                                     Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)));
    ctx1.currentIA().iaid_ = iaid_;
    ctx1.currentIA().type_ = pool_->getType();

    Lease6Collection leases1;
    findReservation(engine, ctx1);
    EXPECT_NO_THROW(leases1 = engine.allocateLeases6(ctx1));
    ASSERT_EQ(1, leases1.size());
    EXPECT_EQ("2001:db8:1::babe", leases1[0]->addr_.toText());

    // Double check that repeating the same duid/type/iaid will end up with
    // the same address.
    AllocEngine::ClientContext6 ctx2(subnet_, duid_, false, false, "", true,
                                     Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)));
    ctx2.currentIA().iaid_ = iaid_;
    ctx2.currentIA().type_ = pool_->getType();

    Lease6Collection leases2;
    findReservation(engine, ctx2);
    EXPECT_NO_THROW(leases2 = engine.allocateLeases6(ctx2));
    EXPECT_EQ(1, leases2.size());
    EXPECT_EQ("2001:db8:1::babe", leases2[0]->addr_.toText());

    // Ok, now the tricky part. Request allocation for the same duid and type, but
    // different iaid. The second address should be assigned.
    AllocEngine::ClientContext6 ctx3(subnet_, duid_, false, false, "", true,
                                     Pkt6Ptr(new Pkt6(DHCPV6_SOLICIT, 1234)));
    ctx3.currentIA().iaid_ = iaid_ + 1;
    ctx3.currentIA().type_ = pool_->getType();

    Lease6Collection leases3;
    findReservation(engine, ctx3);
    EXPECT_NO_THROW(leases3 = engine.allocateLeases6(ctx3));
    ASSERT_EQ(1, leases3.size());
    EXPECT_EQ("2001:db8:1::cafe", leases3[0]->addr_.toText());
}

// Checks whether a single host can have more than one reservation.
TEST_F(AllocEngine6Test, reserved2Addresses) {
    // Create reservation for the client. This is in-pool reservation,
    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    // Two addresses are reserved: 2001:db8:1::babe and 2001:db8:1::cafe
    HostPtr host = createHost6(true, IPv6Resrv::TYPE_NA,
                               IOAddress("2001:db8:1::babe"), 128);

    IPv6Resrv resv2(IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::cafe"), 128);
    host->addReservation(resv2);
    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
    CfgMgr::instance().commit();

    AllocEngine engine(100);

    AllocEngine::ClientContext6 ctx1(subnet_, duid_, false, false, "", false,
                                     Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
    ctx1.currentIA().iaid_ = iaid_;
    ctx1.currentIA().type_ = pool_->getType();

    Lease6Collection leases1;
    findReservation(engine, ctx1);
    EXPECT_NO_THROW(leases1 = engine.allocateLeases6(ctx1));
    ASSERT_EQ(1, leases1.size());
    EXPECT_EQ("2001:db8:1::babe", leases1[0]->addr_.toText());

    // Double check that repeating the same duid/type/iaid will end up with
    // the same address.
    AllocEngine::ClientContext6 ctx2(subnet_, duid_, false, false, "", false,
                                     Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
    ctx2.currentIA().iaid_ = iaid_;
    ctx2.currentIA().type_ = pool_->getType();

    Lease6Collection leases2;
    findReservation(engine, ctx2);
    EXPECT_NO_THROW(leases2 = engine.allocateLeases6(ctx2));
    EXPECT_EQ(1, leases2.size());
    EXPECT_EQ("2001:db8:1::babe", leases2[0]->addr_.toText());

    // Ok, now the tricky part. Request allocation for the same duid and type, but
    // different iaid. The second address should be assigned.
    AllocEngine::ClientContext6 ctx3(subnet_, duid_, false, false, "", false,
                                     Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
    ctx3.currentIA().iaid_ = iaid_ + 1;
    ctx3.currentIA().type_ = pool_->getType();

    Lease6Collection leases3;
    findReservation(engine, ctx3);
    EXPECT_NO_THROW(leases3 = engine.allocateLeases6(ctx3));
    ASSERT_EQ(1, leases3.size());
    EXPECT_EQ("2001:db8:1::cafe", leases3[0]->addr_.toText());
}

// Checks whether address can change during renew (if there is a new
// reservation for this client)
TEST_F(AllocEngine6Test, reservedAddressRenewChange) {

    AllocEngine engine(100);

    Lease6Collection leases;

    leases = allocateTest(engine, pool_, IOAddress("::"), false, true);
    ASSERT_EQ(1, leases.size());
    ASSERT_NE("2001:db8:1::1c", leases[0]->addr_.toText());

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(leases[0]->addr_, 128));

    // Create reservation for the client. This is in-pool reservation,
    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1c"), 128);

    Lease6Collection renewed = renewTest(engine, pool_, hints, IN_SUBNET, IN_POOL);
    ASSERT_EQ(1, renewed.size());
    ASSERT_EQ("2001:db8:1::1c", renewed[0]->addr_.toText());
}

// Checks whether address can change during renew (if there is a new
// reservation for this address for another client)
TEST_F(AllocEngine6Test, reservedAddressRenewReserved) {

    AllocEngine engine(100);

    Lease6Collection leases;

    leases = allocateTest(engine, pool_, IOAddress("::"), false, true);
    ASSERT_EQ(1, leases.size());

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(leases[0]->addr_, 128));

    // Create reservation for this address, but for another client.
    // This is in-pool reservation, as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    HostPtr host = createHost6(false, IPv6Resrv::TYPE_NA, leases[0]->addr_, 128);

    // We need to tweak reservation id: use a different DUID for client Y
    vector<uint8_t> other_duid(8, 0x45);
    host->setIdentifier(&other_duid[0], other_duid.size(), Host::IDENT_DUID);
    // Ok, now add it to the HostMgr
    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
    CfgMgr::instance().commit();

    Lease6Collection renewed = renewTest(engine, pool_, hints, IN_SUBNET, IN_POOL);
    ASSERT_EQ(1, renewed.size());

    // Check that we no longer have the reserved address.
    ASSERT_NE(leases[0]->addr_.toText(), renewed[0]->addr_.toText());

    // Check that the lease for the now reserved address is no longer in
    // the lease database.
    Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
                                                               leases[0]->addr_);
    EXPECT_FALSE(from_mgr);
}

/// @todo: The following methods are tested indirectly by allocateLeases6()
/// tests, but could use more direct testing:
/// - AllocEngine::allocateUnreservedLeases6
/// - AllocEngine::allocateReservedLeases6
/// - AllocEngine::removeNonmatchingReservedLeases6
/// - AllocEngine::removeLeases
/// - AllocEngine::removeNonreservedLeases6

// Checks that a client gets the address reserved (in-pool case)
// This test checks the behavior of the allocation engine in the following
// scenario:
// - Client has no lease in the database.
// - Client has an in-pool reservation.
// - Client sends SOLICIT without any hints.
// - Client is allocated a reserved address.
//
// Note that DHCPv6 client can, but don't have to send any hints in its
// Solicit message.
TEST_F(AllocEngine6Test, reservedAddressByMacInPoolSolicitNoHint) {
    // Create reservation for the client. This is in-pool reservation,
    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    createHost6HWAddr(true, IPv6Resrv::TYPE_NA, hwaddr_,
                      IOAddress("2001:db8:1::1c"), 128);

    AllocEngine engine(100);

    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("::"), true);
    ASSERT_TRUE(lease);
    EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
}

// Checks that a client gets the address reserved (in-pool case)
// This test checks the behavior of the allocation engine in the following
// scenario:
// - Client has no lease in the database.
// - Client has an in-pool reservation.
// - Client sends REQUEST without any hints.
// - Client is allocated a reserved address.
//
// Note that DHCPv6 client must send an address in REQUEST that the server
// offered in Advertise. Nevertheless, the client may ignore this requirement.
TEST_F(AllocEngine6Test, reservedAddressByMacInPoolRequestNoHint) {
    // Create reservation for the client. This is in-pool reservation,
    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    createHost6HWAddr(true, IPv6Resrv::TYPE_NA, hwaddr_,
                      IOAddress("2001:db8:1::1c"), 128);

    AllocEngine engine(100);

    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("::"), false);
    ASSERT_TRUE(lease);
    EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
}

// Checks that a client gets the address reserved (in-pool case)
// This test checks the behavior of the allocation engine in the following
// scenario:
// - Client has no lease in the database.
// - Client has an in-pool reservation.
// - Client sends SOLICIT with a hint that does not match reservation
// - Client is allocated a reserved address, not the hint.
//
// Note that DHCPv6 client can, but don't have to send any hints in its
// Solicit message.
TEST_F(AllocEngine6Test, reservedAddressByMacInPoolSolicitValidHint) {
    // Create reservation for the client. This is in-pool reservation,
    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    createHost6HWAddr(true, IPv6Resrv::TYPE_NA, hwaddr_,
                      IOAddress("2001:db8:1::1c"), 128);

    AllocEngine engine(100);

    // Let's pretend the client sends hint 2001:db8:1::10.
    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::10"), true);
    ASSERT_TRUE(lease);

    // The hint should be ignored and the reserved address should be assigned
    EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
}

// Checks that a client gets the address reserved (in-pool case)
// This test checks the behavior of the allocation engine in the following
// scenario:
// - Client has no lease in the database.
// - Client has an in-pool reservation.
// - Client sends REQUEST with a hint that does not match reservation
// - Client is allocated a reserved address, not the hint.
//
// Note that DHCPv6 client must send an address in REQUEST that the server
// offered in Advertise. Nevertheless, the client may ignore this requirement.
TEST_F(AllocEngine6Test, reservedAddressByMacInPoolRequestValidHint) {
    // Create reservation for the client This is in-pool reservation,
    // as the pool is 2001:db8:1::10 - 2001:db8:1::20.
    createHost6HWAddr(true, IPv6Resrv::TYPE_NA, hwaddr_,
                      IOAddress("2001:db8:1::1c"), 128);

    AllocEngine engine(100);

    // Let's pretend the client sends hint 2001:db8:1::10.
    Lease6Ptr lease = simpleAlloc6Test(pool_, IOAddress("2001:db8:1::10"), false);
    ASSERT_TRUE(lease);

    // The hint should be ignored and the reserved address should be assigned
    EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());
}

// This test checks that the allocation engine can delegate the long prefix.
// The pool with prefix of 64 and with long delegated prefix has a very
// high capacity. The number of attempts that the allocation engine makes
// to allocate the prefix for high capacity pools is equal to the capacity
// value. This test verifies that the prefix can be allocated in that
// case.
TEST_F(AllocEngine6Test, largePdPool) {
    AllocEngine engine(0);

    // Remove the default PD pool.
    subnet_->delPools(Lease::TYPE_PD);

    // Configure the PD pool with the prefix length of /80 and the delegated
    // length /96.
    Pool6Ptr pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"), 80, 96));
    subnet_->addPool(pool);

    // We should have got exactly one lease.
    Lease6Collection leases = allocateTest(engine, pool, IOAddress("::"),
                                           false, true);
    ASSERT_EQ(1, leases.size());
}

// This test checks that the allocation engine can pick a pool which has smaller
// delegated prefix length than the hint.
TEST_F(AllocEngine6Test, largePdPoolPreferLower) {
    AllocEngine engine(0);

    // Remove the default PD pool.
    subnet_->delPools(Lease::TYPE_PD);

    // Configure the PD pool with the prefix length of /80 and the delegated
    // length /96.
    Pool6Ptr pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"), 80, 96));
    subnet_->addPool(pool);

    Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:3::"), 72, 80));
    subnet_->addPool(pool2);

    // We should have got exactly one lease.
    // Even though the hint is from the first pool, the second pool is preferred.
    Lease6Collection leases = allocateTest(engine, pool2, IOAddress("2001:db8:1:2::"),
                                           false, true, 92);
    ASSERT_EQ(1, leases.size());
}

// This test checks that the allocation engine can pick a pool which has smaller
// delegated prefix length than the hint. However the already present lease in
// the database is used and the hint delegated length is ignored.
TEST_F(AllocEngine6Test, largePdPoolPreferExistingInsteadOfLower) {
    AllocEngine engine(0);

    // Remove the default PD pool.
    subnet_->delPools(Lease::TYPE_PD);

    // Configure the PD pool with the prefix length of /80 and the delegated
    // length /96.
    Pool6Ptr pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"), 80, 96));
    subnet_->addPool(pool);

    Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:3::"), 72, 80));
    subnet_->addPool(pool2);

    // Let's create a lease and put it in the LeaseMgr
    // Even if the prefix length in the hint does not match, the allocation
    // engine should use the existing lease.
    Lease6Ptr used(new Lease6(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"),
                              duid_, iaid_, 300, 400, subnet_->getID(), HWAddrPtr(), 96));
    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));

    // We should have got exactly one lease.
    Lease6Collection leases = allocateTest(engine, pool, IOAddress("2001:db8:1:3::"),
                                           false, true, 92);
    ASSERT_EQ(1, leases.size());
}

// This test checks that the allocation engine can pick a pool which has exact
// delegated prefix length as the hint.
TEST_F(AllocEngine6Test, largePdPoolPreferEqual) {
    AllocEngine engine(0);

    // Remove the default PD pool.
    subnet_->delPools(Lease::TYPE_PD);

    // Configure the PD pool with the prefix length of /80 and the delegated
    // length /96.
    Pool6Ptr pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"), 80, 96));
    subnet_->addPool(pool);

    Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:3::"), 72, 80));
    subnet_->addPool(pool2);

    // We should have got exactly one lease.
    // Even though the hint is from the first pool, the second pool is preferred.
    Lease6Collection leases = allocateTest(engine, pool2, IOAddress("2001:db8:1:2::"),
                                           false, true, 80);
    ASSERT_EQ(1, leases.size());
}

// This test checks that the allocation engine can pick a pool which has exact
// delegated prefix length as the hint. However the already present lease in
// the database is used and the hint delegated length is ignored.
TEST_F(AllocEngine6Test, largePdPoolPreferExistingInsteadOfEqual) {
    AllocEngine engine(0);

    // Remove the default PD pool.
    subnet_->delPools(Lease::TYPE_PD);

    // Configure the PD pool with the prefix length of /80 and the delegated
    // length /96.
    Pool6Ptr pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"), 80, 96));
    subnet_->addPool(pool);

    Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:3::"), 72, 80));
    subnet_->addPool(pool2);

    // Let's create a lease and put it in the LeaseMgr
    // Even if the prefix length in the hint does not match, the allocation
    // engine should use the existing lease.
    Lease6Ptr used(new Lease6(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"),
                              duid_, iaid_, 300, 400, subnet_->getID(), HWAddrPtr(), 96));
    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));

    // We should have got exactly one lease.
    Lease6Collection leases = allocateTest(engine, pool, IOAddress("2001:db8:1:3::"),
                                           false, true, 80);
    ASSERT_EQ(1, leases.size());
}

// This test checks that the allocation engine can pick a pool which has greater
// delegated prefix length than the hint.
TEST_F(AllocEngine6Test, largePdPoolPreferHigher) {
    AllocEngine engine(0);

    // Remove the default PD pool.
    subnet_->delPools(Lease::TYPE_PD);

    // Configure the PD pool with the prefix length of /80 and the delegated
    // length /96.
    Pool6Ptr pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"), 80, 96));
    subnet_->addPool(pool);

    Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:3::"), 72, 80));
    subnet_->addPool(pool2);

    // We should have got exactly one lease.
    // Even though the first pool also matches the condition, because of the hint,
    // the second pool is preferred.
    Lease6Collection leases = allocateTest(engine, pool2, IOAddress("2001:db8:1:3::"),
                                           false, true, 64);
    ASSERT_EQ(1, leases.size());
}

// This test checks that the allocation engine can pick a pool which has greater
// delegated prefix length than the hint. However the already present lease in
// the database is used and the hint delegated length is ignored.
TEST_F(AllocEngine6Test, largePdPoolPreferExistingInsteadOfHigher) {
    AllocEngine engine(0);

    // Remove the default PD pool.
    subnet_->delPools(Lease::TYPE_PD);

    // Configure the PD pool with the prefix length of /80 and the delegated
    // length /96.
    Pool6Ptr pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"), 80, 96));
    subnet_->addPool(pool);

    Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1:3::"), 72, 80));
    subnet_->addPool(pool2);

    // Let's create a lease and put it in the LeaseMgr
    // Even if the prefix length in the hint does not match, the allocation
    // engine should use the existing lease.
    Lease6Ptr used(new Lease6(Lease::TYPE_PD, IOAddress("2001:db8:1:2::"),
                              duid_, iaid_, 300, 400, subnet_->getID(), HWAddrPtr(), 96));
    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used));

    // We should have got exactly one lease.
    Lease6Collection leases = allocateTest(engine, pool, IOAddress("2001:db8:1:3::"),
                                           false, true, 64);
    ASSERT_EQ(1, leases.size());
}

// This test checks that the allocation engine can delegate addresses
// from ridiculously large pool. The configuration provides 2^80 or
// 1208925819614629174706176 addresses. We used to have a bug that would
// confuse the allocation engine if the number of available addresses
// was larger than 2^32.
TEST_F(AllocEngine6Test, largePoolOver32bits) {
    AllocEngine engine(0);

    // Configure 2001:db8::/32 subnet
    subnet_ = Subnet6::create(IOAddress("2001:db8::"), 32, 1, 2, 3, 4, SubnetID(10));

    // Configure the NA pool of /48. So there are 2^80 addresses there. Make
    // sure that we still can handle cases where number of available addresses
    // is over max_uint64
    Pool6Ptr pool(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::"), 48));
    subnet_->addPool(pool);

    // We should have got exactly one lease.
    Lease6Collection leases = allocateTest(engine, pool, IOAddress("::"),
                                           false, true);
    ASSERT_EQ(1, leases.size());
}

// This test verifies that it is possible to override the number of allocation
// attempts made by the allocation engine for a single lease.
TEST_F(AllocEngine6Test, largeAllocationAttemptsOverride) {
    // Remove the default NA pools.
    subnet_->delPools(Lease::TYPE_NA);
    subnet_->delPools(Lease::TYPE_PD);

    // Add exactly one pool with many addresses.
    Pool6Ptr pool(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::"), 56));
    subnet_->addPool(pool);

    // Allocate 5 addresses from the pool configured.
    for (int i = 0; i < 5; ++i) {
        DuidPtr duid = DuidPtr(new DUID(vector<uint8_t>(12,
                                                        static_cast<uint8_t>(i))));
        // Get the unique IAID.
        const uint32_t iaid = 3568 + i;

        // Construct the unique address from the pool.
        std::ostringstream address;
        address << "2001:db8:1::";
        address << i;

        // Allocate the lease.
        Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress(address.str()),
                                   duid, iaid, 501, 502, subnet_->getID(), HWAddrPtr()));
        ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));
    }

    // Try to use the allocation engine to allocate the lease. The iterative
    // allocator will pick the addresses already allocated until it finds the
    // available address. Since, we have restricted the number of attempts the
    // allocation should fail.
    AllocEngine engine(3);
    Lease6Collection leases = allocateTest(engine, pool_, IOAddress("::"),
                                           false, true);
    ASSERT_TRUE(leases.empty());

    EXPECT_EQ(1, getStatistics("v6-allocation-fail"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network"));
    EXPECT_EQ(1, getStatistics("v6-allocation-fail-subnet"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes"));

    EXPECT_EQ(1, getStatistics("v6-allocation-fail", subnet_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network", subnet_->getID()));
    EXPECT_EQ(1, getStatistics("v6-allocation-fail-subnet", subnet_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", subnet_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", subnet_->getID()));

    // This time, lets allow more attempts, and expect that the allocation will
    // be successful.
    AllocEngine engine2(6);
    leases = allocateTest(engine2, pool_, IOAddress("::"), false, true);
    ASSERT_EQ(1, leases.size());
}

// This test checks if an expired declined lease can be reused in SOLICIT (fake allocation)
TEST_F(AllocEngine6Test, solicitReuseDeclinedLease6) {

    AllocEnginePtr engine(new AllocEngine(100));
    ASSERT_TRUE(engine);

    // Now prepare a configuration with single address pool.
    // Create one subnet with a pool holding one address.
    string addr_txt("2001:db8:1::ad");
    IOAddress addr(addr_txt);
    initSubnet(IOAddress("2001:db8:1::"), addr, addr);

    // Use information that is different than what we'll request
    Lease6Ptr declined = generateDeclinedLease(addr_txt, 100, -10);
    ASSERT_TRUE(declined->expired());

    // CASE 1: Asking for any address
    Lease6Ptr assigned;
    testReuseLease6(engine, declined, "::", true, SHOULD_PASS, assigned);

    // Check that we got that single lease
    ASSERT_TRUE(assigned);
    EXPECT_EQ(addr, assigned->addr_);

    // Do all checks on the lease (if subnet-id, preferred/valid times are ok etc.)
    checkLease6(duid_, assigned, Lease::TYPE_NA, 128);

    // CASE 2: Asking specifically for this address
    testReuseLease6(engine, declined, addr_txt, true, SHOULD_PASS, assigned);

    // Check that we got that single lease
    ASSERT_TRUE(assigned);
    EXPECT_EQ(addr, assigned->addr_);
}

// This test checks if an expired declined lease can be reused when responding
// to REQUEST (actual allocation)
TEST_F(AllocEngine6Test, requestReuseDeclinedLease6) {

    AllocEnginePtr engine(new AllocEngine(100));
    ASSERT_TRUE(engine);

    // Now prepare a configuration with single address pool.
    string addr_txt("2001:db8::7");
    IOAddress addr(addr_txt);
    initSubnet(IOAddress("2001:db8::"), addr, addr);

    // Now create a declined lease, decline it and rewind its cltt, so it
    // is expired.
    Lease6Ptr declined = generateDeclinedLease(addr_txt, 100, -10);

    // Asking specifically for this address
    Lease6Ptr assigned;
    testReuseLease6(engine, declined, addr_txt, false, SHOULD_PASS, assigned);
    // Check that we got it.
    ASSERT_TRUE(assigned);
    EXPECT_EQ(addr, assigned->addr_);

    // Check that the lease is indeed updated in LeaseMgr
    Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
                                                               addr);
    ASSERT_TRUE(from_mgr);

    // Now check that the lease in LeaseMgr has the same parameters
    detailCompareLease(assigned, from_mgr);
}

// This test checks if statistics are not updated when expired declined lease
// is reused when responding to SOLICIT (fake allocation)
TEST_F(AllocEngine6Test, solicitReuseDeclinedLease6Stats) {

    // Now prepare for SOLICIT processing
    AllocEnginePtr engine(new AllocEngine(100));
    ASSERT_TRUE(engine);

    // Now prepare a configuration with single address pool.
    string addr_txt("2001:db8:1::1");
    IOAddress addr(addr_txt);
    initSubnet(IOAddress("2001:db8:1::"), addr, addr);

    // Stats should be zero.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
    EXPECT_TRUE(testStatistics("declined-addresses", 0));
    EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0));
    EXPECT_TRUE(testStatistics("declined-addresses", 0, subnet_->getID()));
    EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0, subnet_->getID()));

    // Get the cumulative count of assigned addresses.
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    // Now create a declined lease, decline it and rewind its cltt, so it
    // is expired.
    Lease6Ptr declined = generateDeclinedLease(addr_txt, 100, -10);

    // Ask for any address. There's only one address in the pool, so it doesn't
    // matter much.
    Lease6Ptr assigned;
    testReuseLease6(engine, declined, "::", true, SHOULD_PASS, assigned);

    // Check that the stats were not modified
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
    EXPECT_TRUE(testStatistics("declined-addresses", 0));
    EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0));
    EXPECT_TRUE(testStatistics("declined-addresses", 0, subnet_->getID()));
    EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0, subnet_->getID()));
}

// This test checks if statistics are updated when expired declined lease
// is reused when responding to REQUEST (actual allocation)
TEST_F(AllocEngine6Test, requestReuseDeclinedLease6Stats) {

    // Prepare for REQUEST processing.
    AllocEnginePtr engine(new AllocEngine(100));
    ASSERT_TRUE(engine);

    // Now prepare a configuration with single address pool.
    string addr_txt("2001:db8::1");
    IOAddress addr(addr_txt);
    initSubnet(IOAddress("2001:db8::"), addr, addr);

    // Stats should be zero.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
    EXPECT_TRUE(testStatistics("declined-addresses", 0));
    EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0));
    EXPECT_TRUE(testStatistics("declined-addresses", 0, subnet_->getID()));
    EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0, subnet_->getID()));

    // Get the cumulative count of assigned addresses.
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    // Now create a declined lease, decline it and rewind its cltt, so it
    // is expired.
    Lease6Ptr declined = generateDeclinedLease(addr_txt, 100, -10);

    // Ask for any address. There's only one address in the pool, so it doesn't
    // matter much.
    Lease6Ptr assigned;
    testReuseLease6(engine, declined, "::", false, SHOULD_PASS, assigned);

    // Check that the stats were modified as expected.
    // assigned-nas should NOT get incremented. Currently we do not adjust assigned
    // counts when we declines
    // declined-addresses will -1, as the artificial creation of declined lease
    // doesn't increment it from zero.  reclaimed-declined-addresses will be 1
    // because the leases are implicitly reclaimed before they can be assigned.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
    cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    glbl_cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
    EXPECT_TRUE(testStatistics("declined-addresses", -1));
    EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 1));
    EXPECT_TRUE(testStatistics("declined-addresses", -1, subnet_->getID()));
    EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 1, subnet_->getID()));
}

// This test checks if an expired-reclaimed lease can be reused by
// a returning client via REQUEST, rather than renew/rebind.  This
// would be typical of cable modem clients which do not retain lease
// data across reboots.
TEST_F(AllocEngine6Test, reuseReclaimedExpiredViaRequest) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    IOAddress addr("2001:db8:1::ad");
    CfgMgr& cfg_mgr = CfgMgr::instance();
    cfg_mgr.clear(); // Get rid of the default test configuration

    // Create configuration similar to other tests, but with a single address pool
    subnet_ = Subnet6::create(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4, SubnetID(10));
    pool_ = Pool6Ptr(new Pool6(Lease::TYPE_NA, addr, addr)); // just a single address
    subnet_->addPool(pool_);
    cfg_mgr.getStagingCfg()->getCfgSubnets6()->add(subnet_);
    cfg_mgr.commit();

    // Verify relevant stats are zero.
    EXPECT_TRUE(testStatistics("assigned-nas", 0, subnet_->getID()));
    EXPECT_TRUE(testStatistics("reclaimed-leases", 0));
    EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID()));

    // Get the cumulative count of assigned addresses.
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    // Let's create an expired lease
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_,
                               501, 502, subnet_->getID(), HWAddrPtr()));
    lease->cltt_ = time(0) - 500; // Allocated 500 seconds ago
    lease->valid_lft_ = 495; // Lease was valid for 495 seconds
    lease->fqdn_fwd_ = true;
    lease->fqdn_rev_ = true;
    lease->hostname_ = "myhost.example.com.";
    lease->state_ = Lease::STATE_EXPIRED_RECLAIMED;
    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    // Verify that the lease state is indeed expired-reclaimed
    EXPECT_EQ(lease->state_, Lease::STATE_EXPIRED_RECLAIMED);

    // Same client comes along and issues a request
    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
                                    Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));

    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));

    // Check that he got the original lease back.
    ASSERT_TRUE(lease);
    EXPECT_EQ(addr, lease->addr_);

    // Check that the lease is indeed updated in LeaseMgr
    Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(Lease::TYPE_NA,
                                                               addr);
    ASSERT_TRUE(from_mgr);

    // Now check that the lease in LeaseMgr has the same parameters
    detailCompareLease(lease, from_mgr);

    // Verify that the lease state has been set back to the default.
    EXPECT_EQ(lease->state_, Lease::STATE_DEFAULT);

    // Verify assigned-nas got bumped.  Reclaimed stats should still
    // be zero as we artificially marked it reclaimed.
    EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet_->getID()));
    cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet_->getID()));
    glbl_cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));
    EXPECT_TRUE(testStatistics("reclaimed-leases", 0));
    EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID()));
}

/// @brief This test class is dedicated to testing shared networks
///
/// It uses one common configuration:
/// 1 shared network with 2 subnets:
///   - 2001:db8:1::/56 subnet with a small pool of single address
///   - 2001:db8:1::/56 subnet with pool with 64K addresses.
class SharedNetworkAlloc6Test : public AllocEngine6Test {
public:
    SharedNetworkAlloc6Test() : engine_(0) {
        subnet1_ = Subnet6::create(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4, SubnetID(10));
        subnet2_ = Subnet6::create(IOAddress("2001:db8:2::"), 56, 1, 2, 3, 4, SubnetID(20));
        pool1_.reset(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
                               IOAddress("2001:db8:1::1")));
        pool2_.reset(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:2::"),
                               IOAddress("2001:db8:2::FF")));
        subnet1_->addPool(pool1_);
        subnet2_->addPool(pool2_);

        // Both subnets belong to the same network so they can be used
        // interchangeably.
        network_.reset(new SharedNetwork6("test_network"));
        network_->add(subnet1_);
        network_->add(subnet2_);
    }

    Lease6Ptr
    insertLease(std::string addr, SubnetID subnet_id) {
        Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress(addr), duid_, iaid_,
                                   501, 502, subnet_->getID(), HWAddrPtr()));
        lease->cltt_ = time(0) - 10; // Allocated 10 seconds ago
        if (!LeaseMgrFactory::instance().addLease(lease)) {
            ADD_FAILURE() << "Failed to add a lease for address " << addr
                          << " in subnet with subnet-id " << subnet_id;
            return (Lease6Ptr());
        }
        return (lease);
    }

    /// Convenience pointers to configuration elements. These are initialized
    /// in the constructor and are used throughout the tests.
    AllocEngine engine_;
    Subnet6Ptr subnet1_;
    Subnet6Ptr subnet2_;
    Pool6Ptr pool1_;
    Pool6Ptr pool2_;
    SharedNetwork6Ptr network_;
};

// This test verifies that the server can offer an address from a
// subnet and the introduction of shared network doesn't break anything here.
TEST_F(SharedNetworkAlloc6Test, solicitSharedNetworkSimple) {

    // Create context which will be used to try to allocate leases from the
    // shared network. The context points to subnet1, which address space
    // is exhausted. We expect the allocation engine to find another subnet
    // within the same shared network and offer an address from there.
    Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
    AllocEngine::ClientContext6 ctx(subnet1_, duid_, false, false, "", true,
                                    query);
    ctx.currentIA().iaid_ = iaid_;

    Lease6Ptr lease;
    ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
    ASSERT_TRUE(lease);
    ASSERT_TRUE(subnet1_->inRange(lease->addr_));
}

// This test verifies that the server can pick a subnet from shared subnets
// based on hints.
TEST_F(SharedNetworkAlloc6Test, solicitSharedNetworkHint) {

    // Create context which will be used to try to allocate leases from the
    // shared network. There's a hint that points to the subnet2. The
    // shared network mechanism should be able to pick the second subnet
    // based on it.
    Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
    AllocEngine::ClientContext6 ctx(subnet1_, duid_, false, false, "", true,
                                    query);
    ctx.currentIA().iaid_ = iaid_;
    ctx.currentIA().addHint(IOAddress("2001:db8:2::12"));

    Lease6Ptr lease;
    ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
    ASSERT_TRUE(lease);

    // The second subnet should be selected.
    ASSERT_TRUE(subnet2_->inRange(lease->addr_));
}

// This test verifies that the client is offered an address from an
// alternative subnet within shared network when the address pool is
// exhausted in the first address pool.
TEST_F(SharedNetworkAlloc6Test, solicitSharedNetworkOutOfAddresses) {

    // Create a lease for a single address in the first address pool. The
    // pool is now exhausted.
    DuidPtr other_duid(new DUID(vector<uint8_t>(12, 0xff)));
    const uint32_t other_iaid = 3568;
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"),
                               other_duid, other_iaid, 501, 502,
                               subnet1_->getID(), HWAddrPtr()));
    lease->cltt_ = time(0) - 10; // Allocated 10 seconds ago
    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    // Create context which will be used to try to allocate leases from the
    // shared network. The context points to subnet1, which address space
    // is exhausted. We expect the allocation engine to find another subnet
    // within the same shared network and offer an address from there.
    Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
    AllocEngine::ClientContext6 ctx(subnet1_, duid_, false, false, "", true,
                                    query);
    ctx.currentIA().iaid_ = iaid_;

    Lease6Ptr lease2;
    ASSERT_NO_THROW(lease2 = expectOneLease(engine_.allocateLeases6(ctx)));
    ASSERT_TRUE(lease2);
    ASSERT_TRUE(subnet2_->inRange(lease2->addr_));

    // The client having a lease should be offered this lease, even if
    // the client starts allocation from the second subnet. The code should
    // determine that the client has a lease in subnet1 and use this subnet
    // instead.
    AllocEngine::ClientContext6 ctx2(subnet2_, other_duid, false, false, "",
                                     true, query);
    ctx2.currentIA().iaid_ = other_iaid;
    ASSERT_NO_THROW(lease2 = expectOneLease(engine_.allocateLeases6(ctx2)));
    ASSERT_TRUE(lease2);
    ASSERT_EQ("2001:db8:1::1", lease2->addr_.toText());

    // Delete the lease in the first subnet.
    ASSERT_TRUE(LeaseMgrFactory::instance().deleteLease(lease));

    // Now, try requesting this address by providing a hint. The engine
    // should try to honor the hint even though we start from the subnet2.
    ctx.subnet_ = subnet2_;
    ctx.currentIA().addHint(IOAddress("2001:db8:1::1"));
    ASSERT_NO_THROW(lease2 = expectOneLease(engine_.allocateLeases6(ctx)));
    ASSERT_TRUE(lease2);
    ASSERT_TRUE(subnet1_->inRange(lease2->addr_));
}

// This test verifies that the server can offer an address from a
// different subnet than orginally selected, when the address pool in
// the first subnet is exhausted.
TEST_F(SharedNetworkAlloc6Test, solicitSharedNetworkClassification) {
    // Try to offer address from subnet1. There is an address available so
    // it should be offered.
    Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
    AllocEngine::ClientContext6 ctx(subnet1_, duid_, false, false, "", true,
                                    query);
    ctx.currentIA().iaid_ = iaid_;

    Lease6Ptr lease;
    ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
    ASSERT_TRUE(lease);
    ASSERT_TRUE(subnet1_->inRange(lease->addr_));

    // Apply restrictions on the subnet1. This should be only assigned
    // to clients belonging to cable-modem class.
    subnet1_->allowClientClass("cable-modem");

    // The allocation engine should determine that the subnet1 is not
    // available for the client not belonging to the cable-modem class.
    // Instead, it should offer an address from subnet2 that belongs
    // to the same shared network.
    AllocEngine::ClientContext6 ctx2(subnet1_, duid_, false, false, "", true,
                                    query);
    ctx2.currentIA().iaid_ = iaid_;
    ctx2.query_ = query;
    ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx2)));
    ASSERT_TRUE(lease);
    ASSERT_TRUE(subnet2_->inRange(lease->addr_));

    AllocEngine::ClientContext6 ctx3(subnet1_, duid_, false, false, "", true,
                                    query);
    ctx3.currentIA().iaid_ = iaid_;
    ctx3.query_ = query;

    // Create host reservation in the first subnet for this client. The
    // allocation engine should not assign reserved address to the client
    // because client classification doesn't allow that.
    subnet_ = subnet1_;
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1"), 128);
    AllocEngine::findReservation(ctx3);
    ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
    ASSERT_TRUE(lease);
    ASSERT_TRUE(subnet2_->inRange(lease->addr_));

    AllocEngine::ClientContext6 ctx4(subnet1_, duid_, false, false, "", true,
                                    query);
    ctx4.currentIA().iaid_ = iaid_;
    ctx4.query_ = query;

    // Assign cable-modem class and try again. This time, we should
    // offer an address from the subnet1_.
    ctx4.query_->addClass(ClientClass("cable-modem"));

    AllocEngine::findReservation(ctx4);
    ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx4)));
    ASSERT_TRUE(lease);
    EXPECT_EQ("2001:db8:1::1", lease->addr_.toText());
}

// This test verifies that the server can offer an address from a
// different subnet than orginally selected, when the address pool in
// the first subnet requires another class.
TEST_F(SharedNetworkAlloc6Test, solicitSharedNetworkPoolClassification) {
    // Try to offer address from subnet1. There is an address available so
    // it should be offered.
    Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
    AllocEngine::ClientContext6 ctx(subnet1_, duid_, false, false, "", true,
                                    query);
    ctx.currentIA().iaid_ = iaid_;

    Lease6Ptr lease;
    ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
    ASSERT_TRUE(lease);
    ASSERT_TRUE(subnet1_->inRange(lease->addr_));

    // Apply restrictions on the pool1. This should be only assigned
    // to clients belonging to cable-modem class.
    pool1_->allowClientClass("cable-modem");

    // The allocation engine should determine that the pool1 is not
    // available for the client not belonging to the cable-modem class.
    // Instead, it should offer an address from subnet2 that belongs
    // to the same shared network.
    AllocEngine::ClientContext6 ctx2(subnet1_, duid_, false, false, "", true,
                                    query);
    ctx2.currentIA().iaid_ = iaid_;
    ctx2.query_ = query;
    ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx2)));
    ASSERT_TRUE(lease);
    ASSERT_TRUE(subnet2_->inRange(lease->addr_));

    AllocEngine::ClientContext6 ctx3(subnet1_, duid_, false, false, "", true,
                                    query);
    ctx3.currentIA().iaid_ = iaid_;
    ctx3.query_ = query;

    AllocEngine::ClientContext6 ctx4(subnet1_, duid_, false, false, "", true,
                                    query);
    ctx4.currentIA().iaid_ = iaid_;
    ctx4.query_ = query;

    // Assign cable-modem class and try again. This time, we should
    // offer an address from the pool1_.
    ctx4.query_->addClass(ClientClass("cable-modem"));

    // Restrict access to pool2 for this client, to make sure that the
    // server doesn't accidentally get a lease from this pool.
    pool2_->allowClientClass("telephone");

    AllocEngine::findReservation(ctx4);
    ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx4)));
    ASSERT_TRUE(lease);
    EXPECT_EQ("2001:db8:1::1", lease->addr_.toText());
}

// This test verifies that the client is offered a reserved address
// even if this address belongs to another subnet within the same
// shared network.
TEST_F(SharedNetworkAlloc6Test, solicitSharedNetworkReservations) {
    EXPECT_FALSE(HostMgr::instance().getDisableSingleQuery());

    // Create reservation for the client in the second subnet.
    subnet_ = subnet2_;
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:2::15"), 128);

    // Start allocation from subnet1_. The engine should determine that the
    // client has reservations in subnet2_ and should rather assign reserved
    // addresses.
    Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
    AllocEngine::ClientContext6 ctx(subnet1_, duid_, false, false, "", true,
                                    query);
    ctx.currentIA().iaid_ = iaid_;

    // Find reservations for this subnet/shared network.
    AllocEngine::findReservation(ctx);

    Lease6Ptr lease;
    ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
    ASSERT_TRUE(lease);
    ASSERT_EQ("2001:db8:2::15", lease->addr_.toText());
}

// This test verifies that the client is offered a reserved address
// even if this address belongs to another subnet within the same
// shared network. Host lookups returning a collection are disabled.
// As it is only an optimization the behavior (so the test) must stay
// unchanged.
TEST_F(SharedNetworkAlloc6Test, solicitSharedNetworkReservationsNoColl) {
    // Disable host lookups returning a collection.
    ASSERT_FALSE(HostMgr::instance().getDisableSingleQuery());
    HostMgr::instance().setDisableSingleQuery(true);

    // Create reservation for the client in the second subnet.
    subnet_ = subnet2_;
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:2::15"), 128);

    // Start allocation from subnet1_. The engine should determine that the
    // client has reservations in subnet2_ and should rather assign reserved
    // addresses.
    Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234));
    AllocEngine::ClientContext6 ctx(subnet1_, duid_, false, false, "", true,
                                    query);
    ctx.currentIA().iaid_ = iaid_;

    // Find reservations for this subnet/shared network.
    AllocEngine::findReservation(ctx);

    Lease6Ptr lease;
    ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
    ASSERT_TRUE(lease);
    ASSERT_EQ("2001:db8:2::15", lease->addr_.toText());
}

// This test verifies that the client is allocated a reserved address
// even if this address belongs to another subnet within the same
// shared network.
TEST_F(SharedNetworkAlloc6Test, requestSharedNetworkReservations) {
    EXPECT_FALSE(HostMgr::instance().getDisableSingleQuery());

    // Create reservation for the client in the second subnet.
    subnet_ = subnet2_;
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:2::15"), 128);

    // Start allocation from subnet1_. The engine should determine that the
    // client has reservations in subnet2_ and should rather assign reserved
    // addresses.
    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
    AllocEngine::ClientContext6 ctx(subnet1_, duid_, false, false, "", false,
                                    query);
    ctx.currentIA().iaid_ = iaid_;

    // Find reservations for this subnet/shared network.
    AllocEngine::findReservation(ctx);

    Lease6Ptr lease;
    ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
    ASSERT_TRUE(lease);
    ASSERT_EQ("2001:db8:2::15", lease->addr_.toText());
}

// This test verifies that the client is allocated a reserved address
// even if this address belongs to another subnet within the same
// shared network. Host lookups returning a collection are disabled.
// As it is only an optimization the behavior (so the test) must stay
// unchanged.
TEST_F(SharedNetworkAlloc6Test, requestSharedNetworkReservationsNoColl) {
    // Disable host lookups returning a collection.
    ASSERT_FALSE(HostMgr::instance().getDisableSingleQuery());
    HostMgr::instance().setDisableSingleQuery(true);

    // Create reservation for the client in the second subnet.
    subnet_ = subnet2_;
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:2::15"), 128);

    // Start allocation from subnet1_. The engine should determine that the
    // client has reservations in subnet2_ and should rather assign reserved
    // addresses.
    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
    AllocEngine::ClientContext6 ctx(subnet1_, duid_, false, false, "", false,
                                    query);
    ctx.currentIA().iaid_ = iaid_;

    // Find reservations for this subnet/shared network.
    AllocEngine::findReservation(ctx);

    Lease6Ptr lease;
    ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
    ASSERT_TRUE(lease);
    ASSERT_EQ("2001:db8:2::15", lease->addr_.toText());
}

// This test verifies that client is assigned an existing lease from a
// shared network, regardless of the default subnet. It also verifies that
// the client is assigned a reserved address from a shared network which
// replaces existing lease within this shared network.
TEST_F(SharedNetworkAlloc6Test, requestSharedNetworkExistingLeases) {
    // Get the cumulative count of assigned addresses.
    int64_t cumulative = getStatistics("cumulative-assigned-nas",
                                       subnet2_->getID());
    int64_t glbl_cumulative = getStatistics("cumulative-assigned-nas");

    // Create a lease in subnet 2 for this client. The lease is in expired
    // reclaimed state initially to allow for checking whether the lease
    // gets renewed.
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, IOAddress("2001:db8:2::1"),
                               duid_, iaid_, 501, 502,
                               subnet2_->getID(), HWAddrPtr()));
    lease->state_ = Lease::STATE_EXPIRED_RECLAIMED;
    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    // Create context which will be used to try to allocate leases from the
    // shared network. The context points to subnet 1 initially, but the
    // allocation engine should determine that there are existing leases
    // in subnet 2 and renew those.
    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
    AllocEngine::ClientContext6 ctx(subnet1_, duid_, false, false, "", false,
                                    query);
    ctx.currentIA().iaid_ = iaid_;

    // Check that we have been allocated the existing lease.
    Lease6Ptr lease2;
    ASSERT_NO_THROW(lease2 = expectOneLease(engine_.allocateLeases6(ctx)));
    ASSERT_TRUE(lease2);
    EXPECT_EQ("2001:db8:2::1", lease2->addr_.toText());

    // Statistics should be bumped when the lease is re-assigned.
    EXPECT_TRUE(testStatistics("assigned-nas", 1, subnet2_->getID()));
    cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas",
                               cumulative, subnet2_->getID()));
    glbl_cumulative += 1;
    EXPECT_TRUE(testStatistics("cumulative-assigned-nas", glbl_cumulative));

    // Another interesting case is when there is a reservation in a different
    // subnet than the one from which the ease has been assigned.
    subnet_ = subnet1_;
    createHost6(true, IPv6Resrv::TYPE_NA, IOAddress("2001:db8:1::1"), 128);

    // The reserved lease should take precedence.
    ctx.subnet_ = subnet1_;
    ctx.currentIA().iaid_ = iaid_;
    AllocEngine::findReservation(ctx);
    ASSERT_NO_THROW(lease2 = expectOneLease(engine_.allocateLeases6(ctx)));
    ASSERT_TRUE(lease2);
    EXPECT_EQ("2001:db8:1::1", lease2->addr_.toText());

    // The previous lease should have been removed.
    ASSERT_EQ(1, ctx.currentIA().old_leases_.size());
    EXPECT_EQ("2001:db8:2::1", ctx.currentIA().old_leases_[0]->addr_.toText());
}

// This test verifies that the server can offer an address from a shared
// subnet if there's at least 1 address left there, but will not offer
// anything if both subnets are completely full.
TEST_F(SharedNetworkAlloc6Test, requestRunningOut) {

    // Allocate everything in subnet1
    insertLease("2001:db8:1::1", subnet1_->getID());

    // Allocate everything, except one address in subnet2.
    for (int i = 0; i < 255; i++) {
        stringstream tmp;
        tmp << "2001:db8:2::" << hex << i;
        insertLease(tmp.str(), subnet2_->getID());
    }

    // Create context which will be used to try to allocate leases from the
    // shared network. The context points to subnet 1 initially, but the
    // allocation engine should determine that there are existing leases
    // in subnet 2 and renew those.
    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
    AllocEngine::ClientContext6 ctx1(subnet1_, duid_, false, false, "", false,
                                    query);
    ctx1.currentIA().iaid_ = iaid_;

    // Check that we have been allocated the existing lease (there's only
    // one lease left, so we know exactly which one will be given out.
    Lease6Ptr lease;
    ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx1)));
    ASSERT_TRUE(lease);
    EXPECT_EQ("2001:db8:2::ff", lease->addr_.toText());

    // Ok, now try for another client. We should be completely full.
    DuidPtr other_duid(new DUID(vector<uint8_t>(12, 0xff)));
    AllocEngine::ClientContext6 ctx2(subnet2_, other_duid, false, false, "", false,
                                    query);
    Lease6Collection leases = engine_.allocateLeases6(ctx2);

    // Bugger off, we're full!
    ASSERT_TRUE(leases.empty());

    EXPECT_EQ(1, getStatistics("v6-allocation-fail"));
    EXPECT_EQ(1, getStatistics("v6-allocation-fail-shared-network"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools"));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes"));

    EXPECT_EQ(1, getStatistics("v6-allocation-fail", subnet2_->getID()));
    EXPECT_EQ(1, getStatistics("v6-allocation-fail-shared-network", subnet2_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet", subnet2_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", subnet2_->getID()));
    EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", subnet2_->getID()));
}

// Verifies that client with a hostname reservation can
// 1. Get a dynamic lease
// 2. Renew the same lease via REQUEST (calls allocateLease6)
// 3. Renew the same lease via RENEW/REBIND (calls renewLeases6)
// renew a dynamic lease from their selected subnet.
TEST_F(AllocEngine6Test, hostDynamicAddress) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
                          Host::IDENT_DUID, SUBNET_ID_UNUSED, subnet_->getID(),
                          asiolink::IOAddress("0.0.0.0")));
    host->setHostname("host1");

    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
    CfgMgr::instance().commit();

    subnet_->setReservationsInSubnet(true);

    // Create context which will be used to try to allocate leases
    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
    ctx.currentIA().iaid_ = iaid_;

    // Look up the reservation.
    findReservation(*engine, ctx);

    // Make sure we found our host.
    ConstHostPtr current = ctx.currentHost();
    ASSERT_TRUE(current);
    ASSERT_EQ("host1", current->getHostname());

    // Check that we have been allocated a dynamic address.
    Lease6Ptr lease;
    ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
    ASSERT_TRUE(lease);
    EXPECT_EQ("2001:db8:1::10", lease->addr_.toText());

    // We're going to rollback the clock a little so we can verify a renewal.
    ASSERT_NO_FATAL_FAILURE(rollbackPersistedCltt(lease));

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::10"), 128));

    // Set test fixture hostname_ to the expected value. This gets checked in
    // renewTest.
    hostname_ = "host1";

    // Client should receive a lease.
    Lease6Collection renewed = renewTest(*engine, pool_, hints, IN_SUBNET, IN_POOL);
    ASSERT_EQ(1, renewed.size());

    Lease6Ptr renewed_lease = renewed[0];
    EXPECT_EQ("2001:db8:1::10", renewed_lease->addr_.toText());

    // And the lease lifetime should be extended.
    EXPECT_GT(renewed_lease->cltt_, lease->cltt_)
        << "Lease lifetime was not extended, but it should";

    // Now let's verify that if the client returns via SARR, they get the same
    // lease and the cltt gets extended.

    // First, we're going to rollback the clock again so we can verify the
    // allocation updates the expiry.
    ASSERT_NO_FATAL_FAILURE(rollbackPersistedCltt(renewed_lease));

    // Create a new context which will be used to try to allocate leases
    query.reset(new Pkt6(DHCPV6_REQUEST, 1234));
    AllocEngine::ClientContext6 ctx2(subnet_, duid_, false, false, "", false, query);
    ctx2.currentIA().iaid_ = iaid_;

    // Look up the reservation.
    findReservation(*engine, ctx2);

    // Make sure we found our host.
    current = ctx2.currentHost();
    ASSERT_TRUE(current);
    ASSERT_EQ("host1", current->getHostname());

    // Check that we have been allocated the original dynamic address.
    Lease6Ptr lease2;
    ASSERT_NO_THROW(lease2 = expectOneLease(engine->allocateLeases6(ctx2)));
    ASSERT_TRUE(lease2);
    EXPECT_EQ("2001:db8:1::10", lease2->addr_.toText());

    // And the lease lifetime should be extended.
    EXPECT_GT(lease2->cltt_, renewed_lease->cltt_)
        << "Lease lifetime was not extended, but it should";
}

// Verifies that client with a global hostname reservation can:
// 1. Get a dynamic lease
// 2. Renew the same lease via REQUEST (calls allocateLease6)
// 3. Renew the same lease via RENEW/REBIND (calls renewLeases6)
TEST_F(AllocEngine6Test, globalHostDynamicAddress) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
                          Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
                          asiolink::IOAddress("0.0.0.0")));
    host->setHostname("ghost1");

    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
    CfgMgr::instance().commit();

    subnet_->setReservationsGlobal(true);

    // Create context which will be used to try to allocate leases
    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
    ctx.currentIA().iaid_ = iaid_;

    // Look up the reservation.
    findReservation(*engine, ctx);
    // Make sure we found our host.
    ConstHostPtr current = ctx.currentHost();
    ASSERT_TRUE(current);
    ASSERT_EQ("ghost1", current->getHostname());

    // Check that we have been allocated a dynamic address.
    Lease6Ptr lease;
    ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
    ASSERT_TRUE(lease);
    EXPECT_EQ("2001:db8:1::10", lease->addr_.toText());

    // We're going to rollback the clock a little so we can verify a renewal.
    ASSERT_NO_FATAL_FAILURE(rollbackPersistedCltt(lease));

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::10"), 128));

    // Set test fixture hostname_ to the expected value. This gets checked in
    // renewTest.
    hostname_ = "ghost1";

    // Client should receive a lease.
    Lease6Collection renewed = renewTest(*engine, pool_, hints, IN_SUBNET, IN_POOL);
    ASSERT_EQ(1, renewed.size());

    // And the lease lifetime should be extended.
    ASSERT_GT(renewed[0]->cltt_, lease->cltt_)
        << "Lease lifetime was not extended, but it should";

    // Now let's verify that if the client returns via SARR, they get the same
    // lease. Create a new context which will be used to try to allocate leases

    // First, we're going to rollback the clock again so we can verify the
    // allocation updates the expiry.
    Lease6Ptr renewed_lease = renewed[0];
    ASSERT_NO_FATAL_FAILURE(rollbackPersistedCltt(renewed_lease));

    query.reset(new Pkt6(DHCPV6_REQUEST, 1234));
    AllocEngine::ClientContext6 ctx2(subnet_, duid_, false, false, "", false, query);
    ctx2.currentIA().iaid_ = iaid_;

    // Look up the reservation.
    findReservation(*engine, ctx2);

    // Make sure we found our host.
    current = ctx2.currentHost();
    ASSERT_TRUE(current);
    ASSERT_EQ("ghost1", current->getHostname());

    // Check that we have been allocated a dynamic address.
    Lease6Ptr lease2;
    ASSERT_NO_THROW(lease2 = expectOneLease(engine->allocateLeases6(ctx2)));
    ASSERT_TRUE(lease2);
    EXPECT_EQ("2001:db8:1::10", lease2->addr_.toText());

    // And the lease lifetime should be extended.
    EXPECT_GT(lease2->cltt_, renewed_lease->cltt_)
        << "Lease lifetime was not extended, but it should";
}

// Verifies that client with a globally reserved address that is
// outside the selected subnet is given a dynamic address instead.
TEST_F(AllocEngine6Test, globalHostReservedAddress) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
                          Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
                          asiolink::IOAddress("0.0.0.0")));
    host->setHostname("ghost1");
    IPv6Resrv resv(IPv6Resrv::TYPE_NA, asiolink::IOAddress("3001::1"), 128);
    host->addReservation(resv);

    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
    CfgMgr::instance().commit();

    subnet_->setReservationsGlobal(true);

    // Create context which will be used to try to allocate leases
    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
    ctx.currentIA().iaid_ = iaid_;

    // Look up the reservation.
    findReservation(*engine, ctx);
    // Make sure we found our host.
    ConstHostPtr current = ctx.currentHost();
    ASSERT_TRUE(current);
    ASSERT_EQ("ghost1", current->getHostname());

    // Check that we have been allocated a dynamic address.
    Lease6Ptr lease;
    ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
    ASSERT_TRUE(lease);
    EXPECT_NE("3001::1", lease->addr_.toText());
    EXPECT_TRUE(subnet_->inRange(lease->addr_))
                << " address not in range: " << lease->addr_.toText();

    ASSERT_NO_FATAL_FAILURE(rollbackPersistedCltt(lease));
    EXPECT_NO_THROW(LeaseMgrFactory::instance().updateLease6(lease));

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(IOAddress(lease->addr_.toText()), 128));

    // Set test fixture hostname_ to the expected value. This gets checked in
    // renewTest.
    hostname_ = "ghost1";

    // Client should receive a lease.
    Lease6Collection renewed = renewTest(*engine, pool_, hints, IN_SUBNET, IN_POOL);
    ASSERT_EQ(1, renewed.size());

    // And the lease lifetime should be extended.
    EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
        << "Lease lifetime was not extended, but it should";
}

// Verifies that client with a globally reserved address that is
// inside the selected subnet is given that address.
TEST_F(AllocEngine6Test, globalHostReservedMatchingAddress) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
                          Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
                          asiolink::IOAddress("0.0.0.0")));
    host->setHostname("ghost1");
    IPv6Resrv resv(IPv6Resrv::TYPE_NA, asiolink::IOAddress("2001:db8:1::a"), 128);
    host->addReservation(resv);

    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
    CfgMgr::instance().commit();

    subnet_->setReservationsGlobal(true);

    // Create context which will be used to try to allocate leases
    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
    ctx.currentIA().iaid_ = iaid_;

    // Look up the reservation.
    findReservation(*engine, ctx);
    // Make sure we found our host.
    ConstHostPtr current = ctx.currentHost();
    ASSERT_TRUE(current);
    ASSERT_EQ("ghost1", current->getHostname());

    // Check that we have been allocated the fixed address.
    Lease6Ptr lease;
    ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
    ASSERT_TRUE(lease);
    EXPECT_EQ("2001:db8:1::a", lease->addr_.toText());

    // We're going to rollback the clock a little so we can verify a renewal.
    //--lease->cltt_;
    ASSERT_NO_FATAL_FAILURE(rollbackPersistedCltt(lease));

    // We're going to rollback the clock a little so we can verify a renewal.
    --lease->cltt_;
    EXPECT_NO_THROW(LeaseMgrFactory::instance().updateLease6(lease));

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(IOAddress("2008:db8:1::a"), 128));

    // Set test fixture hostname_ to the expected value. This gets checked in
    // renewTest.
    hostname_ = "ghost1";

    // Client should receive a lease.
    Lease6Collection renewed = renewTest(*engine, pool_, hints, IN_SUBNET, !IN_POOL);
    ASSERT_EQ(1, renewed.size());

    // And the lease lifetime should be extended.
    EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
        << "Lease lifetime was not extended, but it should";
}

// Verifies that client with a global prefix reservation can get and
// renew a lease for an arbitrary prefix.
TEST_F(AllocEngine6Test, globalHostReservedPrefix) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
                          Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
                          asiolink::IOAddress("0.0.0.0")));
    host->setHostname("ghost1");
    IPv6Resrv resv(IPv6Resrv::TYPE_PD, asiolink::IOAddress("3001::"), 64);
    host->addReservation(resv);

    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
    CfgMgr::instance().commit();

    subnet_->setReservationsGlobal(true);

    // Create context which will be used to try to allocate leases
    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
    ctx.currentIA().type_ = Lease::TYPE_PD;
    ctx.currentIA().iaid_ = iaid_;

    // Look up the reservation.
    findReservation(*engine, ctx);
    // Make sure we found our host.
    ConstHostPtr current = ctx.currentHost();
    ASSERT_TRUE(current);
    ASSERT_EQ("ghost1", current->getHostname());

    // Check that we have been allocated the fixed address.
    Lease6Ptr lease;
    ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
    ASSERT_TRUE(lease);
    EXPECT_EQ("3001::", lease->addr_.toText());

    // We're going to rollback the clock a little so we can verify a renewal.
    --lease->cltt_;
    EXPECT_NO_THROW(LeaseMgrFactory::instance().updateLease6(lease));

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(IOAddress("3001::"), 64));

    // Set test fixture hostname_ to the expected value. This gets checked via
    // renewTest.
    hostname_ = "ghost1";

    // We need a PD pool to fake renew_test
    Pool6Ptr dummy_pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 64, 64));

    // Client should receive a lease.
    Lease6Collection renewed = renewTest(*engine, dummy_pool, hints, !IN_SUBNET, !IN_POOL);
    ASSERT_EQ(1, renewed.size());

    // And the lease lifetime should be extended.
    EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
        << "Lease lifetime was not extended, but it should";
}

// Verifies that client with a global prefix reservation can get and
// renew a lease for an arbitrary prefix even if using a wrong hint prefix
// length.
TEST_F(AllocEngine6Test, globalHostReservedPrefixDifferentPrefixLen) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
                          Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
                          asiolink::IOAddress("0.0.0.0")));
    host->setHostname("ghost1");
    IPv6Resrv resv(IPv6Resrv::TYPE_PD, asiolink::IOAddress("3001::"), 64);
    host->addReservation(resv);

    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
    CfgMgr::instance().commit();

    subnet_->setReservationsGlobal(true);

    // Create context which will be used to try to allocate leases
    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
    ctx.currentIA().type_ = Lease::TYPE_PD;
    ctx.currentIA().iaid_ = iaid_;
    // Using a different prefix length in the hint should have no effect
    ctx.currentIA().addHint(asiolink::IOAddress("3001::"), 32);

    // Look up the reservation.
    findReservation(*engine, ctx);
    // Make sure we found our host.
    ConstHostPtr current = ctx.currentHost();
    ASSERT_TRUE(current);
    ASSERT_EQ("ghost1", current->getHostname());

    // Check that we have been allocated the fixed address.
    Lease6Ptr lease;
    ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
    ASSERT_TRUE(lease);
    EXPECT_EQ("3001::", lease->addr_.toText());

    // We're going to rollback the clock a little so we can verify a renewal.
    --lease->cltt_;
    EXPECT_NO_THROW(LeaseMgrFactory::instance().updateLease6(lease));

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(IOAddress("3001::"), 64));

    // Set test fixture hostname_ to the expected value. This gets checked via
    // renewTest.
    hostname_ = "ghost1";

    // We need a PD pool to fake renew_test
    Pool6Ptr dummy_pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 64, 64));

    // Client should receive a lease.
    Lease6Collection renewed = renewTest(*engine, dummy_pool, hints, !IN_SUBNET, !IN_POOL);
    ASSERT_EQ(1, renewed.size());

    // And the lease lifetime should be extended.
    EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
        << "Lease lifetime was not extended, but it should";
}

// Verifies that client with a subnet address reservation can get and
// renew a lease for an address in the subnet.
TEST_F(AllocEngine6Test, mixedHostReservedAddress) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
                          Host::IDENT_DUID, SUBNET_ID_UNUSED, subnet_->getID(),
                          asiolink::IOAddress("0.0.0.0")));
    host->setHostname("mhost1");
    IPv6Resrv resv(IPv6Resrv::TYPE_NA, asiolink::IOAddress("2001:db8:1::1c"), 128);
    host->addReservation(resv);

    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
    CfgMgr::instance().commit();

    subnet_->setReservationsGlobal(true);
    subnet_->setReservationsInSubnet(true);
    subnet_->setReservationsOutOfPool(false);

    // Create context which will be used to try to allocate leases
    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
    ctx.currentIA().iaid_ = iaid_;

    // Look up the reservation.
    findReservation(*engine, ctx);
    // Make sure we found our host.
    ConstHostPtr current = ctx.currentHost();
    ASSERT_TRUE(current);
    ASSERT_EQ("mhost1", current->getHostname());

    // Check that we have been allocated the fixed address.
    Lease6Ptr lease;
    ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
    ASSERT_TRUE(lease);
    EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());

    // We're going to rollback the clock a little so we can verify a renewal.
    --lease->cltt_;
    EXPECT_NO_THROW(LeaseMgrFactory::instance().updateLease6(lease));

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::1c"), 128));

    // Set test fixture hostname_ to the expected value. This gets checked in
    // renewTest.
    hostname_ = "mhost1";

    // Client should receive a lease.
    Lease6Collection renewed = renewTest(*engine, pool_, hints, IN_SUBNET, IN_POOL);
    ASSERT_EQ(1, renewed.size());

    // And the lease lifetime should be extended.
    EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
        << "Lease lifetime was not extended, but it should";
}

// Verifies that client with a subnet prefix reservation can get and
// renew a lease for a prefix in the subnet even if using a wrong hint prefix
// length.
TEST_F(AllocEngine6Test, mixedHostReservedPrefix) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
                          Host::IDENT_DUID, SUBNET_ID_UNUSED, subnet_->getID(),
                          asiolink::IOAddress("0.0.0.0")));
    host->setHostname("mhost1");
    IPv6Resrv resv(IPv6Resrv::TYPE_PD, asiolink::IOAddress("2001:db8:1:2::"), 64);
    host->addReservation(resv);

    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
    CfgMgr::instance().commit();

    subnet_->setReservationsGlobal(true);
    subnet_->setReservationsInSubnet(true);

    // Create context which will be used to try to allocate leases
    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
    ctx.currentIA().type_ = Lease::TYPE_PD;
    ctx.currentIA().iaid_ = iaid_;
    // Using a different prefix length in the hint should have no effect
    ctx.currentIA().addHint(asiolink::IOAddress("2001:db8:1:2::"), 32);

    // Look up the reservation.
    findReservation(*engine, ctx);
    // Make sure we found our host.
    ConstHostPtr current = ctx.currentHost();
    ASSERT_TRUE(current);
    ASSERT_EQ("mhost1", current->getHostname());

    // Check that we have been allocated the fixed prefix.
    Lease6Ptr lease;
    ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
    ASSERT_TRUE(lease);
    EXPECT_EQ("2001:db8:1:2::", lease->addr_.toText());

    // We're going to rollback the clock a little so we can verify a renewal.
    --lease->cltt_;
    EXPECT_NO_THROW(LeaseMgrFactory::instance().updateLease6(lease));

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1:2::"), 64));

    // Set test fixture hostname_ to the expected value. This gets checked via
    // renewTest.
    hostname_ = "mhost1";

    // We need a PD pool to fake renew_test
    Pool6Ptr dummy_pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 64, 64));

    // Client should receive a lease.
    Lease6Collection renewed = renewTest(*engine, dummy_pool, hints, IN_SUBNET, IN_POOL);
    ASSERT_EQ(1, renewed.size());

    // And the lease lifetime should be extended.
    EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
        << "Lease lifetime was not extended, but it should";
}

// Verifies that client with a subnet prefix reservation can get and
// renew a lease for a prefix in the subnet.
TEST_F(AllocEngine6Test, mixedHostReservedPrefixDifferentPrefixLen) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
                          Host::IDENT_DUID, SUBNET_ID_UNUSED, subnet_->getID(),
                          asiolink::IOAddress("0.0.0.0")));
    host->setHostname("mhost1");
    IPv6Resrv resv(IPv6Resrv::TYPE_PD, asiolink::IOAddress("2001:db8:1:2::"), 64);
    host->addReservation(resv);

    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
    CfgMgr::instance().commit();

    subnet_->setReservationsGlobal(true);
    subnet_->setReservationsInSubnet(true);

    // Create context which will be used to try to allocate leases
    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
    ctx.currentIA().type_ = Lease::TYPE_PD;
    ctx.currentIA().iaid_ = iaid_;

    // Look up the reservation.
    findReservation(*engine, ctx);
    // Make sure we found our host.
    ConstHostPtr current = ctx.currentHost();
    ASSERT_TRUE(current);
    ASSERT_EQ("mhost1", current->getHostname());

    // Check that we have been allocated the fixed prefix.
    Lease6Ptr lease;
    ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
    ASSERT_TRUE(lease);
    EXPECT_EQ("2001:db8:1:2::", lease->addr_.toText());

    // We're going to rollback the clock a little so we can verify a renewal.
    --lease->cltt_;
    EXPECT_NO_THROW(LeaseMgrFactory::instance().updateLease6(lease));

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1:2::"), 64));

    // Set test fixture hostname_ to the expected value. This gets checked via
    // renewTest.
    hostname_ = "mhost1";

    // We need a PD pool to fake renew_test
    Pool6Ptr dummy_pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 64, 64));

    // Client should receive a lease.
    Lease6Collection renewed = renewTest(*engine, dummy_pool, hints, IN_SUBNET, IN_POOL);
    ASSERT_EQ(1, renewed.size());

    // And the lease lifetime should be extended.
    EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
        << "Lease lifetime was not extended, but it should";
}

// Verifies that client with a subnet and a global address reservation
// can get and renew a lease for an address in the subnet.
TEST_F(AllocEngine6Test, bothHostReservedAddress) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    HostPtr ghost(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
                           Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
                           asiolink::IOAddress("0.0.0.0")));
    ghost->setHostname("ghost1");
    IPv6Resrv gresv(IPv6Resrv::TYPE_NA, asiolink::IOAddress("3001::1"), 128);
    ghost->addReservation(gresv);

    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(ghost);

    HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
                          Host::IDENT_DUID, SUBNET_ID_UNUSED, subnet_->getID(),
                          asiolink::IOAddress("0.0.0.0")));
    host->setHostname("mhost1");
    IPv6Resrv resv(IPv6Resrv::TYPE_NA, asiolink::IOAddress("2001:db8:1::1c"), 128);
    host->addReservation(resv);

    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
    CfgMgr::instance().commit();

    subnet_->setReservationsGlobal(true);
    subnet_->setReservationsInSubnet(true);
    subnet_->setReservationsOutOfPool(false);

    // Create context which will be used to try to allocate leases
    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
    ctx.currentIA().iaid_ = iaid_;

    // Look up the reservation.
    findReservation(*engine, ctx);
    // Make sure we found our host.
    ConstHostPtr current = ctx.currentHost();
    ASSERT_TRUE(current);
    ASSERT_EQ("mhost1", current->getHostname());

    // Check that we have been allocated the fixed address.
    Lease6Ptr lease;
    ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
    ASSERT_TRUE(lease);
    EXPECT_EQ("2001:db8:1::1c", lease->addr_.toText());

    // We're going to rollback the clock a little so we can verify a renewal.
    --lease->cltt_;
    EXPECT_NO_THROW(LeaseMgrFactory::instance().updateLease6(lease));

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1::1c"), 128));

    // Set test fixture hostname_ to the expected value. This gets checked in
    // renewTest.
    hostname_ = "mhost1";

    // Client should receive a lease.
    Lease6Collection renewed = renewTest(*engine, pool_, hints, IN_SUBNET, IN_POOL);
    ASSERT_EQ(1, renewed.size());

    // And the lease lifetime should be extended.
    EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
        << "Lease lifetime was not extended, but it should";
}

// Verifies that client with a subnet and a global prefix reservation
// can get and renew a lease for a prefix in the subnet.
TEST_F(AllocEngine6Test, bothHostReservedPrefix) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    HostPtr ghost(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
                           Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
                           asiolink::IOAddress("0.0.0.0")));
    ghost->setHostname("ghost1");
    IPv6Resrv gresv(IPv6Resrv::TYPE_PD, asiolink::IOAddress("3001::"), 64);
    ghost->addReservation(gresv);

    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(ghost);

    HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
                          Host::IDENT_DUID, SUBNET_ID_UNUSED, subnet_->getID(),
                          asiolink::IOAddress("0.0.0.0")));
    host->setHostname("mhost1");
    IPv6Resrv resv(IPv6Resrv::TYPE_PD, asiolink::IOAddress("2001:db8:1:2::"), 64);
    host->addReservation(resv);

    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
    CfgMgr::instance().commit();

    subnet_->setReservationsGlobal(true);
    subnet_->setReservationsInSubnet(true);

    // Create context which will be used to try to allocate leases
    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
    ctx.currentIA().type_ = Lease::TYPE_PD;
    ctx.currentIA().iaid_ = iaid_;

    // Look up the reservation.
    findReservation(*engine, ctx);
    // Make sure we found our host.
    ConstHostPtr current = ctx.currentHost();
    ASSERT_TRUE(current);
    ASSERT_EQ("mhost1", current->getHostname());

    // Check that we have been allocated the fixed prefix.
    Lease6Ptr lease;
    ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
    ASSERT_TRUE(lease);
    EXPECT_EQ("2001:db8:1:2::", lease->addr_.toText());

    // We're going to rollback the clock a little so we can verify a renewal.
    --lease->cltt_;
    EXPECT_NO_THROW(LeaseMgrFactory::instance().updateLease6(lease));

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1:2::"), 64));

    // Set test fixture hostname_ to the expected value. This gets checked via
    // renewTest.
    hostname_ = "mhost1";

    // We need a PD pool to fake renew_test
    Pool6Ptr dummy_pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 64, 64));

    // Client should receive a lease.
    Lease6Collection renewed = renewTest(*engine, dummy_pool, hints, IN_SUBNET, IN_POOL);
    ASSERT_EQ(1, renewed.size());

    // And the lease lifetime should be extended.
    EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
        << "Lease lifetime was not extended, but it should";
}

// Verifies that client with a subnet and a global prefix reservation
// can get and renew a lease for a prefix in the subnet even if using a wrong
// hint prefix length.
TEST_F(AllocEngine6Test, bothHostReservedPrefixDifferentPrefixLen) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    HostPtr ghost(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
                           Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
                           asiolink::IOAddress("0.0.0.0")));
    ghost->setHostname("ghost1");
    IPv6Resrv gresv(IPv6Resrv::TYPE_PD, asiolink::IOAddress("3001::"), 64);
    ghost->addReservation(gresv);

    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(ghost);

    HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
                          Host::IDENT_DUID, SUBNET_ID_UNUSED, subnet_->getID(),
                          asiolink::IOAddress("0.0.0.0")));
    host->setHostname("mhost1");
    IPv6Resrv resv(IPv6Resrv::TYPE_PD, asiolink::IOAddress("2001:db8:1:2::"), 64);
    host->addReservation(resv);

    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
    CfgMgr::instance().commit();

    subnet_->setReservationsGlobal(true);
    subnet_->setReservationsInSubnet(true);

    // Create context which will be used to try to allocate leases
    Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234));
    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query);
    ctx.currentIA().type_ = Lease::TYPE_PD;
    ctx.currentIA().iaid_ = iaid_;
    // Using a different prefix length in the hint should have no effect
    ctx.currentIA().addHint(asiolink::IOAddress("2001:db8:1:2::"), 32);

    // Look up the reservation.
    findReservation(*engine, ctx);
    // Make sure we found our host.
    ConstHostPtr current = ctx.currentHost();
    ASSERT_TRUE(current);
    ASSERT_EQ("mhost1", current->getHostname());

    // Check that we have been allocated the fixed prefix.
    Lease6Ptr lease;
    ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
    ASSERT_TRUE(lease);
    EXPECT_EQ("2001:db8:1:2::", lease->addr_.toText());

    // We're going to rollback the clock a little so we can verify a renewal.
    --lease->cltt_;
    EXPECT_NO_THROW(LeaseMgrFactory::instance().updateLease6(lease));

    // This is what the client will send in his renew message.
    AllocEngine::HintContainer hints;
    hints.push_back(AllocEngine::Resource(IOAddress("2001:db8:1:2::"), 64));

    // Set test fixture hostname_ to the expected value. This gets checked via
    // renewTest.
    hostname_ = "mhost1";

    // We need a PD pool to fake renew_test
    Pool6Ptr dummy_pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 64, 64));

    // Client should receive a lease.
    Lease6Collection renewed = renewTest(*engine, dummy_pool, hints, IN_SUBNET, IN_POOL);
    ASSERT_EQ(1, renewed.size());

    // And the lease lifetime should be extended.
    EXPECT_GT(renewed[0]->cltt_, lease->cltt_)
        << "Lease lifetime was not extended, but it should";
}

/// @brief Test fixture class for testing storage of extended lease data.
/// It primarily creates several configuration items common to the
/// extended info tests.
class AllocEngine6ExtendedInfoTest : public AllocEngine6Test {
public:
    /// @brief Constructor
    AllocEngine6ExtendedInfoTest()
        : engine_(100),
          duid1_(), duid2_(), duid3_(), relay1_(), relay2_(), relay3_(),
          duid1_addr_("::"), duid2_addr_("::") {
        duid1_.reset(new DUID(std::vector<uint8_t>(8, 0x84)));
        duid2_.reset(new DUID(std::vector<uint8_t>(8, 0x74)));
        duid3_.reset(new DUID(std::vector<uint8_t>(8, 0x64)));

        relay1_.msg_type_ = DHCPV6_RELAY_FORW;
        relay1_.hop_count_ = 33;
        relay1_.linkaddr_ = IOAddress("2001:db8::1");
        relay1_.peeraddr_ = IOAddress("2001:db8::2");
        relay1_.relay_msg_len_ = 0;

        uint8_t relay_opt_data[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
        vector<uint8_t> relay_data(relay_opt_data,
                                   relay_opt_data + sizeof(relay_opt_data));
        OptionPtr optRelay1(new Option(Option::V6, 200, relay_data));

        relay1_.options_.insert(make_pair(optRelay1->getType(), optRelay1));

        relay2_.msg_type_ = DHCPV6_RELAY_FORW;
        relay2_.hop_count_ = 77;
        relay2_.linkaddr_ = IOAddress("2001:db8::3");
        relay2_.peeraddr_ = IOAddress("2001:db8::4");
        relay2_.relay_msg_len_ = 0;

        relay3_.msg_type_ = DHCPV6_RELAY_FORW;
        relay3_.hop_count_ = 100;
        relay3_.linkaddr_ = IOAddress("2001:db8::5");
        relay3_.peeraddr_ = IOAddress("2001:db8::6");
        relay1_.relay_msg_len_ = 0;

        vector<uint8_t> remote_id_data({1, 2, 3, 4, 5, 6});
        OptionPtr remote_id(new Option(Option::V6, D6O_REMOTE_ID, remote_id_data));
        relay3_.options_.insert(make_pair(remote_id->getType(), remote_id));

        OptionPtr relay_id(new Option(Option::V6, D6O_RELAY_ID, duid3_->getDuid()));
        relay3_.options_.insert(make_pair(relay_id->getType(), relay_id));

        duid1_addr_ = IOAddress("2001:db8:1::10");
        duid2_addr_ = IOAddress("2001:db8:1::11");

        // Create the allocation engine, context and lease.
        NakedAllocEngine engine(100);
    }

    /// Configuration elements. These are initialized in the constructor
    /// and are used throughout the tests.
    NakedAllocEngine engine_;
    DuidPtr duid1_;
    DuidPtr duid2_;
    DuidPtr duid3_;
    Pkt6::RelayInfo relay1_;
    Pkt6::RelayInfo relay2_;
    Pkt6::RelayInfo relay3_;
    IOAddress duid1_addr_;
    IOAddress duid2_addr_;
};

// Exercises AllocEnginer6Test::updateExtendedInfo6() through various
// permutations of client packet content.
TEST_F(AllocEngine6ExtendedInfoTest, updateExtendedInfo6) {
    // Structure that defines a test scenario.
    struct Scenario {
        std::string description_;       // test description
        std::string orig_context_json_; // user context the lease begins with
        std::vector<Pkt6::RelayInfo> relays_; // vector of relays from pkt
        std::string exp_context_json_;  // expected user context on the lease
        bool exp_ret;                   // expected returned value
    };

    // Test scenarios.
    std::vector<Scenario> scenarios {
    {
        "no context, no relay",
        "",
        {},
        "",
        false
    },
    {
        "some original context, no relay",
        "{\"foo\": 123}",
        {},
        "{\"foo\": 123}",
        false
    },
    {
        "no original context, one relay",
        "",
        { relay1_ },
        "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
        " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" } ] } }",
        true
    },
    {
        "no original context, one relay with remote and relay ids",
        "",
        { relay3_ },
        "{ \"ISC\": { \"relay-info\": [ { \"hop\": 100, \"link\": \"2001:db8::5\","
        " \"options\": \"0x00250006010203040506003500086464646464646464\","
        " \"remote-id\": \"010203040506\","
        " \"relay-id\": \"6464646464646464\","
        " \"peer\": \"2001:db8::6\" } ] } }",
        true
    },
    {
        "some original context, one relay",
        "{\"foo\": 123, \"ISC\": {\"bar\": 456}}",
        { relay1_ },
        "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
        " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" } ],"
        "\"bar\": 456 }, \"foo\": 123 }",
        true
    },
    {
        "bad original context, one relay",
        "[\"foo\"]",
        { relay1_ },
        "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
        " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" } ] } }",
        true
    },
    {
        "some original context, one relay",
        "{\"foo\": 123, \"ISC\":[\"bar\"]}",
        { relay1_ },
        "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
        " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" } ] },"
        " \"foo\": 123 }",
        true
    },
    {
        "some original context, one relay with remote and relay ids",
        "{\"foo\": 123}",
        { relay3_ },
        "{ \"ISC\": { \"relay-info\": [ { \"hop\": 100, \"link\": \"2001:db8::5\","
        " \"options\": \"0x00250006010203040506003500086464646464646464\","
        " \"remote-id\": \"010203040506\","
        " \"relay-id\": \"6464646464646464\","
        " \"peer\": \"2001:db8::6\" } ] }, \"foo\": 123 }",
        true
    },
    {
        "no original context, two relay-info",
        "",
        { relay1_, relay2_ },
        "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
        " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" },"
        " {\"hop\": 77, \"link\": \"2001:db8::3\", \"peer\": \"2001:db8::4\" } ] } }",
        true
    },
    {
        "no original context, two relay-info, second with remote and relay ids",
        "",
        { relay1_, relay3_ },
        "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
        " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" },"
        " { \"hop\": 100, \"link\": \"2001:db8::5\","
        " \"options\": \"0x00250006010203040506003500086464646464646464\","
        " \"remote-id\": \"010203040506\","
        " \"relay-id\": \"6464646464646464\","
        " \"peer\": \"2001:db8::6\" } ] } }",
        true
    },
    {
        "original relay context, no relay",
        "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
        " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" } ] } }",
        {},
        "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
        " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" } ] } }",
        false
    },
    {
        "original relay context, different relay",
        "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
        " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" } ] } }",
        { relay2_ },
        "{ \"ISC\": { \"relay-info\": [ { \"hop\": 77, \"link\": \"2001:db8::3\","
        " \"peer\": \"2001:db8::4\" } ] } }",
        true
    },
    {
        "original relay context, different relay with remote and relay ids",
        "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
        " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" } ] } }",
        { relay3_ },
        "{ \"ISC\": { \"relay-info\": [ { \"hop\": 100, \"link\": \"2001:db8::5\","
        " \"options\": \"0x00250006010203040506003500086464646464646464\","
        " \"remote-id\": \"010203040506\","
        " \"relay-id\": \"6464646464646464\","
        " \"peer\": \"2001:db8::6\" } ] } }",
        true
    }
    };

    // Allocate a lease.
    Lease6Ptr lease;
    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
                                    Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 1234)));
    ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
    ASSERT_TRUE(lease);

    // All scenarios require storage to be enabled.
    subnet_->setStoreExtendedInfo(true);

    // Verify that the lease begins with no user context.
    ConstElementPtr user_context = lease->getContext();
    ASSERT_FALSE(user_context);

    // Iterate over the test scenarios.
    ElementPtr orig_context;
    ElementPtr exp_context;
    for (auto const& scenario : scenarios) {
        SCOPED_TRACE(scenario.description_);

        // Create the original user context from JSON.
        if (scenario.orig_context_json_.empty()) {
            orig_context.reset();
        } else {
            ASSERT_NO_THROW(orig_context = Element::fromJSON(scenario.orig_context_json_))
                            << "invalid orig_context_json_, test is broken";
        }

        // Create the expected user context from JSON.
        if (scenario.exp_context_json_.empty()) {
            exp_context.reset();
        } else {
            ASSERT_NO_THROW(exp_context = Element::fromJSON(scenario.exp_context_json_))
                            << "invalid exp_context_json_, test is broken";
        }

        // Initialize lease's user context.
        lease->setContext(orig_context);
        if (!orig_context) {
            ASSERT_FALSE(lease->getContext());
        } else {
            ASSERT_TRUE(lease->getContext());
            ASSERT_TRUE(orig_context->equals(*(lease->getContext())));
        }

        // Set the client packet relay vector from the scenario.
        ctx.query_->relay_info_ = scenario.relays_;

        // Call AllocEngine::updateLease6ExtendeInfo().
        ASSERT_NO_THROW_LOG(engine_.callUpdateLease6ExtendedInfo(lease, ctx));
        bool ret = (lease->extended_info_action_ == Lease6::ACTION_UPDATE);
        // Reset the lease action.
        lease->extended_info_action_ = Lease6::ACTION_IGNORE;
        ASSERT_EQ(scenario.exp_ret, ret);

        // Verify the lease has the expected user context content.
        if (!exp_context) {
            ASSERT_FALSE(lease->getContext());
        } else {
            ASSERT_TRUE(lease->getContext());
            ASSERT_TRUE(exp_context->equals(*(lease->getContext())))
                << "expected: " << *(exp_context) << std::endl
                << "  actual: " << *(lease->getContext()) << std::endl;
        }
    }
}

// Verifies that the extended data (RelayInfos for now) is
// added to a V6 lease when leases are created and/or renewed,
// when store-extended-info is true.
TEST_F(AllocEngine6ExtendedInfoTest, storeExtendedInfoEnabled6) {
    // Structure that defines a test scenario.
    struct Scenario {
        std::string description_;             // test description
        DuidPtr duid_;                        // client DUID
        std::vector<Pkt6::RelayInfo> relays_; // vector of relays from pkt
        std::string exp_context_json_;        // expected user context on the lease
        IOAddress exp_address_;               // expected lease address
    };

    // Test scenarios.
    std::vector<Scenario> scenarios {
    {
        "create client one without relays",
        duid1_,
        {},
        "",
        duid1_addr_
    },
    {
        "renew client one without relays",
        DuidPtr(),
        {},
        "",
        duid1_addr_
    },
    {
        "create client two with relays",
        duid2_,
        { relay1_, relay2_ },
        "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
        " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" },"
        " { \"hop\": 77, \"link\": \"2001:db8::3\", \"peer\": \"2001:db8::4\" } ] } }",
        duid2_addr_
    },
    {
        "renew client two without rai",
        DuidPtr(),
        {},
        "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
        " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" },"
        " { \"hop\": 77, \"link\": \"2001:db8::3\", \"peer\": \"2001:db8::4\" } ] } }",
        duid2_addr_
    }};

    // All of the scenarios require storage to be enabled.
    subnet_->setStoreExtendedInfo(true);

    // Iterate over the test scenarios.
    DuidPtr current_duid;
    for (auto const& scenario : scenarios) {
        SCOPED_TRACE(scenario.description_);

        ElementPtr exp_context;
        // Create the expected user context from JSON.
        if (!scenario.exp_context_json_.empty()) {
            ASSERT_NO_THROW(exp_context = Element::fromJSON(scenario.exp_context_json_))
                            << "invalid exp_context_json_, test is broken";
        }

        Pkt6Ptr pkt;
        if (scenario.duid_) {
            current_duid = scenario.duid_;
            pkt.reset(new Pkt6(DHCPV6_REQUEST, 1234));
        } else {
            pkt.reset(new Pkt6(DHCPV6_RENEW, 1234));
        }

        // Set packet relay vector from the scenario.
        pkt->relay_info_ = scenario.relays_;

        // Create the context;
        AllocEngine::ClientContext6 ctx(subnet_, current_duid, false, false, "", false, pkt);

        // Create or renew the lease.
        Lease6Ptr lease;
        ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
        ASSERT_TRUE(lease);

        EXPECT_EQ(scenario.exp_address_, lease->addr_);

        // Verify the lease has the expected user context content.
        if (!exp_context) {
            ASSERT_FALSE(lease->getContext());
        } else {
            ASSERT_TRUE(lease->getContext());
            ASSERT_TRUE(exp_context->equals(*(lease->getContext())))
                << "expected: " << *(exp_context) << std::endl
                << "  actual: " << *(lease->getContext()) << std::endl;
        }
    }
}

// Verifies that the extended data (RelayInfos for now) is
// not added to a V6 lease when leases are created and/or renewed,
// when store-extended-info is false.
TEST_F(AllocEngine6ExtendedInfoTest, storeExtendedInfoDisabled6) {
    // Structure that defines a test scenario.
    struct Scenario {
        std::string description_;             // test description
        DuidPtr duid_;                        // client DUID
        std::vector<Pkt6::RelayInfo> relays_; // vector of relays from pkt
        IOAddress exp_address_;               // expected lease address
    };

    // Test scenarios.
    std::vector<Scenario> scenarios {
    {
        "create client one without relays",
        duid1_,
        {},
        duid1_addr_
    },
    {
        "renew client one without relays",
        DuidPtr(),
        {},
        duid1_addr_
    },
    {
        "create client two with relays",
        duid2_,
        { relay1_, relay2_ },
        duid2_addr_
    },
    {
        "renew client two with relays",
        DuidPtr(),
        { relay1_, relay2_ },
        duid2_addr_
    }
    };

    // All of the scenarios require storage to be disabled.
    subnet_->setStoreExtendedInfo(false);

    // Iterate over the test scenarios.
    DuidPtr current_duid;
    for (auto const& scenario : scenarios) {
        SCOPED_TRACE(scenario.description_);

        Pkt6Ptr pkt;
        if (scenario.duid_) {
            current_duid = scenario.duid_;
            pkt.reset(new Pkt6(DHCPV6_REQUEST, 1234));
        } else {
            pkt.reset(new Pkt6(DHCPV6_RENEW, 1234));
        }

        // Set packet relay vector from the scenario.
        pkt->relay_info_ = scenario.relays_;

        // Create the context;
        AllocEngine::ClientContext6 ctx(subnet_, current_duid, false, false, "", false, pkt);

        // Create or renew the lease.
        Lease6Ptr lease;
        ASSERT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));
        ASSERT_TRUE(lease);

        EXPECT_EQ(scenario.exp_address_, lease->addr_);

        // Verify the lease had no user context content.
        ASSERT_FALSE(lease->getContext());
    }
}

// Verifies that the extended data (RelayInfos for now) is
// added to a V6 lease when an expired lease is reused and
// store-extended-info is true.  We don't bother testing the
// disabled case as this is tested thoroughly elsewhere.
TEST_F(AllocEngine6ExtendedInfoTest, reuseExpiredLease6) {
    // Create one subnet with a pool holding one address.
    IOAddress addr("2001:db8:1::ad");
    initSubnet(IOAddress("2001:db8:1::"), addr, addr);
    subnet_->setPreferred(Triplet<uint32_t>(200, 300, 400));
    subnet_->setValid(Triplet<uint32_t>(300, 400, 500));

    // Create an expired lease for duid1_.
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid1_, 1234,
                               501, 502, subnet_->getID(), HWAddrPtr()));
    lease->cltt_ = time(0) - 500; // Allocated 500 seconds ago
    lease->valid_lft_ = 495;         // Lease was valid for 495 seconds
    ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease));

    // Make sure that we really created expired lease
    ASSERT_TRUE(lease->expired());

    // Asking specifically for this address with zero lifetimes
    AllocEngine::ClientContext6 ctx(subnet_, duid2_, false, false, "", false,
                                     Pkt6Ptr(new Pkt6(DHCPV6_REQUEST, 5678)));
    ctx.currentIA().iaid_ = iaid_;
    ctx.currentIA().addHint(addr, 128, 0, 0);

    // Add a relay to the packet relay vector.
    ctx.query_->relay_info_.push_back(relay1_);

    // Enable extended info storage.
    subnet_->setStoreExtendedInfo(true);

    // Reuse the expired lease.
    EXPECT_NO_THROW(lease = expectOneLease(engine_.allocateLeases6(ctx)));

    // Check that we got that single lease
    ASSERT_TRUE(lease);
    EXPECT_EQ(addr, lease->addr_);

    // Now let's verify that the extended info is in the user-context.
    ASSERT_TRUE(lease->getContext());
    std::string exp_content_json =
        "{ \"ISC\": { \"relay-info\": [ { \"hop\": 33, \"link\": \"2001:db8::1\","
        " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" } ] } }";
    ConstElementPtr exp_context;
    ASSERT_NO_THROW(exp_context = Element::fromJSON(exp_content_json))
                            << "invalid exp_context_json_, test is broken";
    ASSERT_TRUE(exp_context->equals(*(lease->getContext())))
                << "expected: " << *(exp_context) << std::endl
                << "  actual: " << *(lease->getContext()) << std::endl;
}

// 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(100)));
    ASSERT_TRUE(engine);

    // Set the threshold to 25%.
    subnet_->setCacheThreshold(.25);

    IOAddress addr("2001:db8:1::15");
    time_t now = time(0) - 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)));
    ASSERT_TRUE(lease);
    EXPECT_EQ(addr, lease->addr_);
    EXPECT_EQ(128, lease->prefixlen_);

    // The lease was not reused.
    EXPECT_EQ(0, lease->reuseable_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) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    // Set the threshold to 33%.
    subnet_->setCacheThreshold(.33);

    IOAddress addr("2001:db8:1::15");
    time_t now = time(0) - 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 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)));
    ASSERT_TRUE(lease);
    EXPECT_EQ(addr, lease->addr_);
    EXPECT_EQ(128, lease->prefixlen_);

    // The lease was reused.
    time_t age = lease->cltt_ - now;
    EXPECT_GE(age, 100);
    EXPECT_LE(age, 110);
    EXPECT_EQ(400 - age, lease->reuseable_valid_lft_);
    EXPECT_EQ(300 - age, lease->reuseable_preferred_lft_);

    // Check other lease parameters.
    EXPECT_TRUE(*lease->duid_ == *duid_);
    EXPECT_TRUE(ctx.isAllocated(addr));

    // 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 (renew) using cache threshold.
TEST_F(AllocEngine6Test, renewCacheThreshold6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    // Set the threshold to 25%.
    subnet_->setCacheThreshold(.25);

    IOAddress prefix("2001:db8:1:2::");
    uint8_t prefixlen = 80;
    time_t now = time(0) - 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());
    // 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 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)));
    ASSERT_TRUE(lease);
    EXPECT_EQ(prefix, lease->addr_);
    EXPECT_EQ(prefixlen, lease->prefixlen_);

    // The lease was reused.
    time_t age = lease->cltt_ - now;
    EXPECT_GE(age, 100);
    EXPECT_LE(age, 110);
    EXPECT_EQ(400 - age, lease->reuseable_valid_lft_);
    EXPECT_EQ(300 - age, lease->reuseable_preferred_lft_);

    // Check other lease parameters.
    EXPECT_TRUE(*lease->duid_ == *duid_);
    EXPECT_TRUE(ctx.isAllocated(prefix, prefixlen));

    // 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 max age.
TEST_F(AllocEngine6Test, requestCacheMaxAge6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    // Set the max age to 150.
    subnet_->setCacheMaxAge(150);

    IOAddress addr("2001:db8:1::15");
    time_t now = time(0) - 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 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)));
    ASSERT_TRUE(lease);
    EXPECT_EQ(addr, lease->addr_);
    EXPECT_EQ(128, lease->prefixlen_);

    // The lease was reused.
    time_t age = lease->cltt_ - now;
    EXPECT_GE(age, 100);
    EXPECT_LE(age, 110);
    EXPECT_EQ(400 - age, lease->reuseable_valid_lft_);
    EXPECT_EQ(300 - age, lease->reuseable_preferred_lft_);

    // Check other lease parameters.
    EXPECT_TRUE(*lease->duid_ == *duid_);
    EXPECT_TRUE(ctx.isAllocated(addr));

    // 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 (renew) using cache max age.
TEST_F(AllocEngine6Test, renewCacheMaxAge6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    // Set the max age to 150.
    subnet_->setCacheMaxAge(150);

    IOAddress prefix("2001:db8:1:2::");
    uint8_t prefixlen = 80;
    time_t now = time(0) - 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());
    // 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 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)));
    ASSERT_TRUE(lease);
    EXPECT_EQ(prefix, lease->addr_);
    EXPECT_EQ(prefixlen, lease->prefixlen_);

    // The lease was reused.
    time_t age = lease->cltt_ - now;
    EXPECT_GE(age, 100);
    EXPECT_LE(age, 110);
    EXPECT_EQ(400 - age, lease->reuseable_valid_lft_);
    EXPECT_EQ(300 - age, lease->reuseable_preferred_lft_);

    // Check other lease parameters.
    EXPECT_TRUE(*lease->duid_ == *duid_);
    EXPECT_TRUE(ctx.isAllocated(prefix, prefixlen));

    // 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 both cache threshold
// and max age.
TEST_F(AllocEngine6Test, requestCacheBoth6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    // Set the threshold to 25%.
    subnet_->setCacheThreshold(.25);

    // Set the max age to 150.
    subnet_->setCacheMaxAge(150);

    IOAddress addr("2001:db8:1::15");
    time_t now = time(0) - 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 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)));
    ASSERT_TRUE(lease);
    EXPECT_EQ(addr, lease->addr_);
    EXPECT_EQ(128, lease->prefixlen_);

    // The lease was reused.
    time_t age = lease->cltt_ - now;
    EXPECT_GE(age, 100);
    EXPECT_LE(age, 110);
    EXPECT_EQ(400 - age, lease->reuseable_valid_lft_);
    EXPECT_EQ(300 - age, lease->reuseable_preferred_lft_);

    // Check other lease parameters.
    EXPECT_TRUE(*lease->duid_ == *duid_);
    EXPECT_TRUE(ctx.isAllocated(addr));

    // 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 (renew) using both cache threshold
// and max age.
TEST_F(AllocEngine6Test, renewCacheBoth6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    // Set the threshold to 25%.
    subnet_->setCacheThreshold(.25);

    // Set the max age to 150.
    subnet_->setCacheMaxAge(150);

    IOAddress prefix("2001:db8:1:2::");
    uint8_t prefixlen = 80;
    time_t now = time(0) - 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());
    // 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 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)));
    ASSERT_TRUE(lease);
    EXPECT_EQ(prefix, lease->addr_);
    EXPECT_EQ(prefixlen, lease->prefixlen_);

    // The lease was reused.
    time_t age = lease->cltt_ - now;
    EXPECT_GE(age, 100);
    EXPECT_LE(age, 110);
    EXPECT_EQ(400 - age, lease->reuseable_valid_lft_);
    EXPECT_EQ(300 - age, lease->reuseable_preferred_lft_);

    // Check other lease parameters.
    EXPECT_TRUE(*lease->duid_ == *duid_);
    EXPECT_TRUE(ctx.isAllocated(prefix, prefixlen));

    // 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't be reused (request) using too small
// cache threshold.
TEST_F(AllocEngine6Test, requestCacheBadThreshold6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    // 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(0) - 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)));
    ASSERT_TRUE(lease);
    EXPECT_EQ(addr, lease->addr_);
    EXPECT_EQ(128, lease->prefixlen_);

    // The lease was not reused.
    EXPECT_EQ(0, lease->reuseable_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 threshold.
TEST_F(AllocEngine6Test, renewCacheBadThreshold6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    // 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(0) - 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)));
    ASSERT_TRUE(lease);
    EXPECT_EQ(prefix, lease->addr_);
    EXPECT_EQ(prefixlen, lease->prefixlen_);

    // The lease was not reused.
    EXPECT_EQ(0, lease->reuseable_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(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(0) - 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)));
    ASSERT_TRUE(lease);
    EXPECT_EQ(addr, lease->addr_);
    EXPECT_EQ(128, lease->prefixlen_);

    // The lease was not reused.
    EXPECT_EQ(0, lease->reuseable_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(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(0) - 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)));
    ASSERT_TRUE(lease);
    EXPECT_EQ(prefix, lease->addr_);
    EXPECT_EQ(prefixlen, lease->prefixlen_);

    // The lease was not reused.
    EXPECT_EQ(0, lease->reuseable_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) when the valid
// lifetime was reduced.
// This works only when the lifetime is recomputed.
TEST_F(AllocEngine6Test, renewCacheReducedValid6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    // Set valid lifetime to 200.
    subnet_->setValid(200);

    // Set the threshold to 25%.
    subnet_->setCacheThreshold(.25);

    IOAddress addr("2001:db8:1::15");
    time_t now = time(0) - 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 renew.
    Pkt6Ptr query(new Pkt6(DHCPV6_RENEW, 1234));
    AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false,
                                    query);
    ctx.currentIA().iaid_ = iaid_;
    ctx.currentIA().addHint(addr);

    EXPECT_NO_THROW(lease = expectOneLease(engine->renewLeases6(ctx)));
    ASSERT_TRUE(lease);
    EXPECT_EQ(addr, lease->addr_);
    EXPECT_EQ(128, lease->prefixlen_);

    // The lease was not reused.
    EXPECT_EQ(0, lease->reuseable_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) when the preferred
// lifetime was reduced.
// This works only when the lifetime is recomputed.
TEST_F(AllocEngine6Test, renewCacheReducedPreferred6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    // Set preferred lifetime to 100.
    subnet_->setPreferred(100);

    // Set the threshold to 25%.
    subnet_->setCacheThreshold(.25);

    IOAddress prefix("2001:db8:1:2::");
    uint8_t prefixlen = 80;
    time_t now = time(0) - 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)));
    ASSERT_TRUE(lease);
    EXPECT_EQ(prefix, lease->addr_);
    EXPECT_EQ(prefixlen, lease->prefixlen_);

    // The lease was not reused.
    EXPECT_EQ(0, lease->reuseable_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) when DDNS parameter changed.
TEST_F(AllocEngine6Test, requestCacheFwdDDNS6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    // Set the threshold to 25%.
    subnet_->setCacheThreshold(.25);

    IOAddress addr("2001:db8:1::15");
    time_t now = time(0) - 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_, true, false, "", false,
                                    query);
    ctx.currentIA().iaid_ = iaid_;
    ctx.currentIA().addHint(addr);

    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
    ASSERT_TRUE(lease);
    EXPECT_EQ(addr, lease->addr_);
    EXPECT_EQ(128, lease->prefixlen_);

    // The lease was not reused.
    EXPECT_EQ(0, lease->reuseable_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) when DDNS parameter changed.
TEST_F(AllocEngine6Test, renewCacheFwdDDNS6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    // Set the threshold to 25%.
    subnet_->setCacheThreshold(.25);

    IOAddress prefix("2001:db8:1:2::");
    uint8_t prefixlen = 80;
    time_t now = time(0) - 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_, true, 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)));
    ASSERT_TRUE(lease);
    EXPECT_EQ(prefix, lease->addr_);
    EXPECT_EQ(prefixlen, lease->prefixlen_);

    // The lease was not reused.
    EXPECT_EQ(0, lease->reuseable_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) when DDNS parameter changed.
TEST_F(AllocEngine6Test, requestCacheRevDDNS6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    // Set the threshold to 25%.
    subnet_->setCacheThreshold(.25);

    IOAddress addr("2001:db8:1::15");
    time_t now = time(0) - 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, true, "", false,
                                    query);
    ctx.currentIA().iaid_ = iaid_;
    ctx.currentIA().addHint(addr);

    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
    ASSERT_TRUE(lease);
    EXPECT_EQ(addr, lease->addr_);
    EXPECT_EQ(128, lease->prefixlen_);

    // The lease was not reused.
    EXPECT_EQ(0, lease->reuseable_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) when DDNS parameter changed.
TEST_F(AllocEngine6Test, renewCacheRevDDNS6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    // Set the threshold to 25%.
    subnet_->setCacheThreshold(.25);

    IOAddress prefix("2001:db8:1:2::");
    uint8_t prefixlen = 80;
    time_t now = time(0) - 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, true, "", 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)));
    ASSERT_TRUE(lease);
    EXPECT_EQ(prefix, lease->addr_);
    EXPECT_EQ(prefixlen, lease->prefixlen_);

    // The lease was not reused.
    EXPECT_EQ(0, lease->reuseable_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) when hostname changed.
TEST_F(AllocEngine6Test, requestCacheHostname6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    // Set the threshold to 25%.
    subnet_->setCacheThreshold(.25);

    IOAddress addr("2001:db8:1::15");
    time_t now = time(0) - 100; // Allocated 100 seconds ago.
    Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_,
                               300, 400, subnet_->getID(),
                               false, false, "foo"));
    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, "bar", false,
                                    query);
    ctx.currentIA().iaid_ = iaid_;
    ctx.currentIA().addHint(addr);

    EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
    ASSERT_TRUE(lease);
    EXPECT_EQ(addr, lease->addr_);
    EXPECT_EQ(128, lease->prefixlen_);

    // The lease was not reused.
    EXPECT_EQ(0, lease->reuseable_valid_lft_);
    EXPECT_EQ("bar", lease->hostname_);

    // 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) when hostname changed.
TEST_F(AllocEngine6Test, renewCacheHostname6) {
    boost::scoped_ptr<AllocEngine> engine;
    ASSERT_NO_THROW(engine.reset(new AllocEngine(100)));
    ASSERT_TRUE(engine);

    // Set the threshold to 25%.
    subnet_->setCacheThreshold(.25);

    IOAddress prefix("2001:db8:1:2::");
    uint8_t prefixlen = 80;
    time_t now = time(0) - 100; // Allocated 100 seconds ago.
    Lease6Ptr lease(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid_,
                               300, 400, subnet_->getID(),
                               false, false, "foo",
                               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, "bar", 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)));
    ASSERT_TRUE(lease);
    EXPECT_EQ(prefix, lease->addr_);
    EXPECT_EQ(prefixlen, lease->prefixlen_);

    // The lease was not reused.
    EXPECT_EQ(0, lease->reuseable_valid_lft_);
    EXPECT_EQ("bar", lease->hostname_);

    // 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);
}

// Verifies that AllocEngine::getLifetimes6() returns the appropriate
// valid lifetime value based on the context content.
TEST_F(AllocEngine6Test, getValidLifetime) {
    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();

    ClientClassDefPtr class_def(new ClientClassDef("valid_one", ExpressionPtr()));
    Triplet<uint32_t> valid_one(50, 100, 150);
    class_def->setValid(valid_one);
    dictionary->addClass(class_def);

    class_def.reset(new ClientClassDef("valid_two", ExpressionPtr()));
    Triplet<uint32_t>valid_two(200, 250, 300);
    class_def->setValid(valid_two);
    dictionary->addClass(class_def);

    class_def.reset(new ClientClassDef("valid_unspec", ExpressionPtr()));
    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 exp_valid_;                // expected lifetime
    };

    // Scenarios to test.
    std::vector<Scenario> scenarios = {
        {
            "no classes, no hint",
            {},
            0,
            subnet_->getValid()
        },
        {
            "no classes, hint",
            {},
            subnet_->getValid().getMin() + 50,
            subnet_->getValid().getMin() + 50
        },
        {
            "no classes, hint too small",
            {},
            subnet_->getValid().getMin() - 50,
            subnet_->getValid().getMin()
        },
        {
            "no classes, hint too big",
            {},
            subnet_->getValid().getMax() + 50,
            subnet_->getValid().getMax()
        },
        {
            "class unspecified, no hint",
            { "valid_unspec" },
            0,
            subnet_->getValid()
        },
        {
            "from last class, no hint",
            { "valid_unspec", "valid_one" },
            0,
            valid_one.get()
        },
        {
            "from first class, no hint",
            { "valid_two", "valid_one" },
            0,
            valid_two.get()
        },
        {
            "class plus hint",
            { "valid_one" },
            valid_one.getMin() + 25,
            valid_one.getMin() + 25
        },
        {
            "class plus hint too small",
            { "valid_one" },
            valid_one.getMin() - 25,
            valid_one.getMin()
        },
        {
            "class plus hint too big",
            { "valid_one" },
            valid_one.getMax() + 25,
            valid_one.getMax()
        }
    };

    // 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_);

            Lease6Ptr lease;
            ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
            ASSERT_TRUE(lease);
            EXPECT_EQ(lease->valid_lft_, scenario.exp_valid_);
        }
    }
}

// Verifies that AllocEngine::getLifetimes6() returns the appropriate
// valid lifetime value based on the context content.
TEST_F(AllocEngine6Test, getTemplateClassValidLifetime) {
    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 exp_valid_;                // expected lifetime
    };

    // Scenarios to test.
    std::vector<Scenario> scenarios = {
        {
            "no classes, no hint",
            {},
            0,
            subnet_->getValid()
        },
        {
            "no classes, hint",
            {},
            subnet_->getValid().getMin() + 50,
            subnet_->getValid().getMin() + 50
        },
        {
            "no classes, hint too small",
            {},
            subnet_->getValid().getMin() - 50,
            subnet_->getValid().getMin()
        },
        {
            "no classes, hint too big",
            {},
            subnet_->getValid().getMax() + 50,
            subnet_->getValid().getMax()
        },
        {
            "class unspecified, no hint",
            { "valid_unspec" },
            0,
            subnet_->getValid()
        },
        {
            "from last class, no hint",
            { "valid_unspec", "valid_one" },
            0,
            valid_one.get()
        },
        {
            "from first class, no hint",
            { "valid_two", "valid_one" },
            0,
            valid_two.get()
        },
        {
            "class plus hint",
            { "valid_one" },
            valid_one.getMin() + 25,
            valid_one.getMin() + 25
        },
        {
            "class plus hint too small",
            { "valid_one" },
            valid_one.getMin() - 25,
            valid_one.getMin()
        },
        {
            "class plus hint too big",
            { "valid_one" },
            valid_one.getMax() + 25,
            valid_one.getMax()
        }
    };

    // 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_);

            Lease6Ptr lease;
            ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
            ASSERT_TRUE(lease);
            EXPECT_EQ(lease->valid_lft_, scenario.exp_valid_);
        }
    }
}

// Verifies that AllocEngine::getLifetimes6() returns the appropriate
// preferred lifetime value based on the context content.
TEST_F(AllocEngine6Test, getPreferredLifetime) {
    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();

    ClientClassDefPtr class_def(new ClientClassDef("preferred_one", ExpressionPtr()));
    Triplet<uint32_t> preferred_one(50, 100, 150);
    class_def->setPreferred(preferred_one);
    dictionary->addClass(class_def);

    class_def.reset(new ClientClassDef("preferred_two", ExpressionPtr()));
    Triplet<uint32_t>preferred_two(200, 250, 300);
    class_def->setPreferred(preferred_two);
    dictionary->addClass(class_def);

    class_def.reset(new ClientClassDef("preferred_unspec", ExpressionPtr()));
    dictionary->addClass(class_def);

    // Commit our class changes.
    CfgMgr::instance().commit();

    // Ensure subnet valid-lft is 400.
    subnet_->setValid(Triplet<uint32_t>(400, 400, 400));

    // Convenience Triplet values.
    Triplet<uint32_t> unspecified;
    Triplet<uint32_t> specified(Triplet<uint32_t>(301, 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
        Triplet<uint32_t> subnet_pref_;     // subnet's preferred lifetime triplet.
        uint32_t requested_lft_;            // use as option 51 is > 0
        uint32_t exp_preferred_;            // expected lifetime
    };

    // Scenarios to test.
    std::vector<Scenario> scenarios = {
        {
            "no classes, no hint, subnet specified",
            {},
            specified,
            0,
            specified
        },
        {
            "no classes, no hint, subnet unspecified",
            {},
            unspecified,
            0,
            (subnet_->getValid() * 5 / 8)
        },
        {
            "no classes, hint",
            {},
            specified,
            subnet_->getPreferred().getMin() + 50,
            subnet_->getPreferred().getMin() + 50
        },
        {
            "no classes, hint too small",
            {},
            specified,
            specified.getMin() - 50,
            specified.getMin()
        },
        {
            "no classes, hint too big",
            {},
            specified,
            specified.getMax() + 50,
            (subnet_->getValid() * 5 / 8)
        },
        {
            "class unspecified, no hint",
            { "preferred_unspec" },
            specified,
            0,
            specified
        },
        {
            "from last class, no hint",
            { "preferred_unspec", "preferred_one" },
            specified,
            0,
            preferred_one.get()
        },
        {
            "from first class, no hint",
            { "preferred_two", "preferred_one" },
            specified,
            0,
            preferred_two.get()
        },
        {
            "class plus hint",
            { "preferred_one" },
            specified,
            preferred_one.getMin() + 25,
            preferred_one.getMin() + 25
        },
        {
            "class plus hint too small",
            { "preferred_one" },
            specified,
            preferred_one.getMin() - 25,
            preferred_one.getMin()
        },
        {
            "class plus hint too big",
            { "preferred_one" },
            specified,
            preferred_one.getMax() + 25,
            preferred_one.getMax()
        }
    };

    // Iterate over the scenarios and verify the correct outcome.
    for (auto const& scenario : scenarios) {
        SCOPED_TRACE(scenario.desc_); {
            // Set the subnet's preferred-lifetime triplet.
            subnet_->setPreferred(scenario.subnet_pref_);

            // 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);

            Lease6Ptr lease;
            ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
            ASSERT_TRUE(lease);
            EXPECT_EQ(lease->preferred_lft_, scenario.exp_preferred_);
        }
    }
}

// Verifies that AllocEngine::getLifetimes6() returns the appropriate
// preferred lifetime value based on the context content.
TEST_F(AllocEngine6Test, getTemplateClassPreferredLifetime) {
    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 max is
    // intentionally larger than valid-lft.
    subnet_->setPreferred(Triplet<uint32_t>(301, 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 exp_preferred_;            // expected lifetime
    };

    // Scenarios to test.
    std::vector<Scenario> scenarios = {
        {
            "no classes, no hint",
            {},
            0,
            subnet_->getPreferred()
        },
        {
            "no classes, hint",
            {},
            subnet_->getPreferred().getMin() + 50,
            subnet_->getPreferred().getMin() + 50
        },
        {
            "no classes, hint too small",
            {},
            subnet_->getPreferred().getMin() - 50,
            subnet_->getPreferred().getMin()
        },
        {
            "no classes, hint too big",
            {},
            subnet_->getPreferred().getMax() + 50,
            (subnet_->getValid() * 5 / 8)
        },
        {
            "class unspecified, no hint",
            { "preferred_unspec" },
            0,
            subnet_->getPreferred()
        },
        {
            "from last class, no hint",
            { "preferred_unspec", "preferred_one" },
            0,
            preferred_one.get()
        },
        {
            "from first class, no hint",
            { "preferred_two", "preferred_one" },
            0,
            preferred_two.get()
        },
        {
            "class plus hint",
            { "preferred_one" },
            preferred_one.getMin() + 25,
            preferred_one.getMin() + 25
        },
        {
            "class plus hint too small",
            { "preferred_one" },
            preferred_one.getMin() - 25,
            preferred_one.getMin()
        },
        {
            "class plus hint too big",
            { "preferred_one" },
            preferred_one.getMax() + 25,
            preferred_one.getMax()
        }
    };

    // 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);

            Lease6Ptr lease;
            ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx)));
            ASSERT_TRUE(lease);
            EXPECT_EQ(lease->preferred_lft_, scenario.exp_preferred_);
        }
    }
}

}  // namespace test
}  // namespace dhcp
}  // namespace isc
