diff options
Diffstat (limited to 'src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc')
-rw-r--r-- | src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc | 5603 |
1 files changed, 5603 insertions, 0 deletions
diff --git a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc new file mode 100644 index 0000000..d03a8a8 --- /dev/null +++ b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc @@ -0,0 +1,5603 @@ +// Copyright (C) 2015-2022 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/host_mgr.h> +#include <dhcpsrv/tests/alloc_engine_utils.h> +#include <dhcpsrv/testutils/test_utils.h> +#include <stats/stats_mgr.h> +#include <testutils/gtest_utils.h> + +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 { + +// 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; + + // Hashed and random allocators are not supported yet + ASSERT_THROW(x.reset(new AllocEngine(AllocEngine::ALLOC_HASHED, 5)), NotImplemented); + ASSERT_THROW(x.reset(new AllocEngine(AllocEngine::ALLOC_RANDOM, 5)), NotImplemented); + + ASSERT_NO_THROW(x.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100, true))); + + // Check that allocator for normal addresses is created + ASSERT_TRUE(x->getAllocator(Lease::TYPE_NA)); + + // Check that allocator for temporary address is created + ASSERT_TRUE(x->getAllocator(Lease::TYPE_TA)); + + // Check that allocator for prefixes is created + ASSERT_TRUE(x->getAllocator(Lease::TYPE_PD)); + + // There should be no V4 allocator + EXPECT_THROW(x->getAllocator(Lease::TYPE_V4), BadValue); +} + +// 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. + pool_->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_EQ(cumulative, + getStatistics("cumulative-assigned-nas", subnet_->getID())); + EXPECT_EQ(glbl_cumulative, getStatistics("cumulative-assigned-nas")); +} + +// 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_EQ(cumulative, + getStatistics("cumulative-assigned-pds", subnet_->getID())); + EXPECT_EQ(glbl_cumulative, getStatistics("cumulative-assigned-pds")); +} + +// 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(AllocEngine::ALLOC_ITERATIVE, 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", 1)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network", 1)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet", 1)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", 1)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", 1)); + + // 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", 1)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network", 1)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet", 1)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", 1)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", 1)); +} + +// This test verifies that the allocator picks addresses that belong to the +// pool +TEST_F(AllocEngine6Test, IterativeAllocator) { + boost::scoped_ptr<NakedAllocEngine::Allocator> + alloc(new NakedAllocEngine::IterativeAllocator(Lease::TYPE_NA)); + + for (int i = 0; i < 1000; ++i) { + IOAddress candidate = alloc->pickAddress(subnet_, cc_, + duid_, IOAddress("::")); + EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate)); + } +} + +// This test verifies that the allocator picks addresses that belong to the +// pool using classification +TEST_F(AllocEngine6Test, IterativeAllocator_class) { + boost::scoped_ptr<NakedAllocEngine::Allocator> + alloc(new NakedAllocEngine::IterativeAllocator(Lease::TYPE_NA)); + + // Restrict pool_ to the foo class. Add a second pool with bar class. + pool_->allowClientClass("foo"); + Pool6Ptr pool(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::100"), + IOAddress("2001:db8:1::109"))); + pool->allowClientClass("bar"); + subnet_->addPool(pool); + + // Clients are in bar + cc_.insert("bar"); + + for (int i = 0; i < 1000; ++i) { + IOAddress candidate = alloc->pickAddress(subnet_, cc_, + duid_, IOAddress("::")); + EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate)); + EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate, cc_)); + } +} + +TEST_F(AllocEngine6Test, IterativeAllocatorAddrStep) { + NakedAllocEngine::NakedIterativeAllocator alloc(Lease::TYPE_NA); + + subnet_->delPools(Lease::TYPE_NA); // Get rid of default pool + + Pool6Ptr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"), + IOAddress("2001:db8:1::5"))); + Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::100"), + IOAddress("2001:db8:1::100"))); + Pool6Ptr pool3(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::105"), + IOAddress("2001:db8:1::106"))); + subnet_->addPool(pool1); + subnet_->addPool(pool2); + subnet_->addPool(pool3); + + // Let's check the first pool (5 addresses here) + EXPECT_EQ("2001:db8:1::1", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::2", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::3", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::4", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::5", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // The second pool is easy - only one address here + EXPECT_EQ("2001:db8:1::100", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // This is the third and last pool, with 2 addresses in it + EXPECT_EQ("2001:db8:1::105", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::106", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // We iterated over all addresses and reached to the end of the last pool. + // Let's wrap around and start from the beginning + EXPECT_EQ("2001:db8:1::1", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::2", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); +} + +TEST_F(AllocEngine6Test, IterativeAllocatorAddrStepInClass) { + NakedAllocEngine::NakedIterativeAllocator alloc(Lease::TYPE_NA); + + subnet_->delPools(Lease::TYPE_NA); // Get rid of default pool + + Pool6Ptr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"), + IOAddress("2001:db8:1::5"))); + Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::100"), + IOAddress("2001:db8:1::100"))); + Pool6Ptr pool3(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::105"), + IOAddress("2001:db8:1::106"))); + // Set pool1 and pool3 but not pool2 in foo class + pool1->allowClientClass("foo"); + pool3->allowClientClass("foo"); + subnet_->addPool(pool1); + subnet_->addPool(pool2); + subnet_->addPool(pool3); + + // Clients are in foo + cc_.insert("foo"); + + // Let's check the first pool (5 addresses here) + EXPECT_EQ("2001:db8:1::1", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::2", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::3", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::4", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::5", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // The second pool is easy - only one address here + EXPECT_EQ("2001:db8:1::100", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // This is the third and last pool, with 2 addresses in it + EXPECT_EQ("2001:db8:1::105", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::106", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // We iterated over all addresses and reached to the end of the last pool. + // Let's wrap around and start from the beginning + EXPECT_EQ("2001:db8:1::1", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::2", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); +} + +TEST_F(AllocEngine6Test, IterativeAllocatorAddrStepOutClass) { + NakedAllocEngine::NakedIterativeAllocator alloc(Lease::TYPE_NA); + + subnet_->delPools(Lease::TYPE_NA); // Get rid of default pool + + Pool6Ptr pool1(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::1"), + IOAddress("2001:db8:1::5"))); + Pool6Ptr pool2(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::100"), + IOAddress("2001:db8:1::100"))); + Pool6Ptr pool3(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::105"), + IOAddress("2001:db8:1::106"))); + // Set pool2 in foo + pool2->allowClientClass("foo"); + subnet_->addPool(pool1); + subnet_->addPool(pool2); + subnet_->addPool(pool3); + + // Let's check the first pool (5 addresses here) + EXPECT_EQ("2001:db8:1::1", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::2", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::3", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::4", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::5", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // The second pool is skipped + + // This is the third and last pool, with 2 addresses in it + EXPECT_EQ("2001:db8:1::105", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::106", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // We iterated over all addresses and reached to the end of the last pool. + // Let's wrap around and start from the beginning + EXPECT_EQ("2001:db8:1::1", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:1::2", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); +} + +TEST_F(AllocEngine6Test, IterativeAllocatorPrefixStep) { + NakedAllocEngine::NakedIterativeAllocator alloc(Lease::TYPE_PD); + + subnet_.reset(new Subnet6(IOAddress("2001:db8::"), 32, 1, 2, 3, 4)); + + Pool6Ptr pool1(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 56, 60)); + Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1::"), 48, 48)); + Pool6Ptr pool3(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:2::"), 56, 64)); + subnet_->addPool(pool1); + subnet_->addPool(pool2); + subnet_->addPool(pool3); + + // We have a 2001:db8::/48 subnet that has 3 pools defined in it: + // 2001:db8::/56 split into /60 prefixes (16 leases) (or 2001:db8:0:X0::) + // 2001:db8:1::/48 split into a single /48 prefix (just 1 lease) + // 2001:db8:2::/56 split into /64 prefixes (256 leases) (or 2001:db8:2:XX::) + + // First pool check (Let's check over all 16 leases) + EXPECT_EQ("2001:db8::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:10::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:20::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:30::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:40::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:50::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:60::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:70::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:80::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:90::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:a0::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:b0::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:c0::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:d0::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:e0::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:f0::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // Second pool (just one lease here) + EXPECT_EQ("2001:db8:1::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // Third pool (256 leases, let's check first and last explicitly and the + // rest over in a pool + EXPECT_EQ("2001:db8:2::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + for (int i = 1; i < 255; i++) { + stringstream exp; + exp << "2001:db8:2:" << hex << i << dec << "::"; + EXPECT_EQ(exp.str(), + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + } + EXPECT_EQ("2001:db8:2:ff::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // Ok, we've iterated over all prefixes in all pools. We now wrap around. + // We're looping over now (iterating over first pool again) + EXPECT_EQ("2001:db8::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:10::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); +} + +TEST_F(AllocEngine6Test, IterativeAllocatorPrefixStepInClass) { + NakedAllocEngine::NakedIterativeAllocator alloc(Lease::TYPE_PD); + + subnet_.reset(new Subnet6(IOAddress("2001:db8::"), 32, 1, 2, 3, 4)); + + Pool6Ptr pool1(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 56, 60)); + Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1::"), 48, 48)); + Pool6Ptr pool3(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:2::"), 56, 64)); + // Set pool1 and pool3 but not pool2 in foo class + pool1->allowClientClass("foo"); + pool3->allowClientClass("foo"); + subnet_->addPool(pool1); + subnet_->addPool(pool2); + subnet_->addPool(pool3); + + // Clients are in foo + cc_.insert("foo"); + + // We have a 2001:db8::/48 subnet that has 3 pools defined in it: + // 2001:db8::/56 split into /60 prefixes (16 leases) (or 2001:db8:0:X0::) + // 2001:db8:1::/48 split into a single /48 prefix (just 1 lease) + // 2001:db8:2::/56 split into /64 prefixes (256 leases) (or 2001:db8:2:XX::) + + // First pool check (Let's check over all 16 leases) + EXPECT_EQ("2001:db8::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:10::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:20::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:30::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:40::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:50::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:60::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:70::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:80::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:90::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:a0::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:b0::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:c0::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:d0::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:e0::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:f0::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // Second pool (just one lease here) + EXPECT_EQ("2001:db8:1::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // Third pool (256 leases, let's check first and last explicitly and the + // rest over in a pool + EXPECT_EQ("2001:db8:2::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + for (int i = 1; i < 255; i++) { + stringstream exp; + exp << "2001:db8:2:" << hex << i << dec << "::"; + EXPECT_EQ(exp.str(), + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + } + EXPECT_EQ("2001:db8:2:ff::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // Ok, we've iterated over all prefixes in all pools. We now wrap around. + // We're looping over now (iterating over first pool again) + EXPECT_EQ("2001:db8::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:10::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); +} + +TEST_F(AllocEngine6Test, IterativeAllocatorPrefixStepOutClass) { + NakedAllocEngine::NakedIterativeAllocator alloc(Lease::TYPE_PD); + + subnet_.reset(new Subnet6(IOAddress("2001:db8::"), 32, 1, 2, 3, 4)); + + Pool6Ptr pool1(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 56, 60)); + Pool6Ptr pool2(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:1::"), 48, 48)); + Pool6Ptr pool3(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8:2::"), 56, 64)); + // Set pool2 in foo + pool2->allowClientClass("foo"); + subnet_->addPool(pool1); + subnet_->addPool(pool2); + subnet_->addPool(pool3); + + // We have a 2001:db8::/48 subnet that has 3 pools defined in it: + // 2001:db8::/56 split into /60 prefixes (16 leases) (or 2001:db8:0:X0::) + // 2001:db8:1::/48 split into a single /48 prefix (just 1 lease) + // 2001:db8:2::/56 split into /64 prefixes (256 leases) (or 2001:db8:2:XX::) + + // First pool check (Let's check over all 16 leases) + EXPECT_EQ("2001:db8::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:10::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:20::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:30::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:40::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:50::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:60::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:70::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:80::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:90::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:a0::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:b0::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:c0::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:d0::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:e0::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:f0::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // The second pool is skipped + + // Third pool (256 leases, let's check first and last explicitly and the + // rest over in a pool + EXPECT_EQ("2001:db8:2::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + for (int i = 1; i < 255; i++) { + stringstream exp; + exp << "2001:db8:2:" << hex << i << dec << "::"; + EXPECT_EQ(exp.str(), + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + } + EXPECT_EQ("2001:db8:2:ff::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + + // Ok, we've iterated over all prefixes in all pools. We now wrap around. + // We're looping over now (iterating over first pool again) + EXPECT_EQ("2001:db8::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); + EXPECT_EQ("2001:db8:0:10::", + alloc.pickAddress(subnet_, cc_, duid_, IOAddress("::")).toText()); +} + +// This test verifies that the iterative allocator can step over addresses +TEST_F(AllocEngine6Test, IterativeAllocatorAddressIncrease) { + NakedAllocEngine::NakedIterativeAllocator alloc(Lease::TYPE_NA); + + // Let's pick the first address + IOAddress addr1 = alloc.pickAddress(subnet_, cc_, duid_, IOAddress("2001:db8:1::10")); + + // Check that we can indeed pick the first address from the pool + EXPECT_EQ("2001:db8:1::10", addr1.toText()); + + // Check that addresses can be increased properly + checkAddrIncrease(alloc, "2001:db8::9", "2001:db8::a"); + checkAddrIncrease(alloc, "2001:db8::f", "2001:db8::10"); + checkAddrIncrease(alloc, "2001:db8::10", "2001:db8::11"); + checkAddrIncrease(alloc, "2001:db8::ff", "2001:db8::100"); + checkAddrIncrease(alloc, "2001:db8::ffff", "2001:db8::1:0"); + checkAddrIncrease(alloc, "::", "::1"); + checkAddrIncrease(alloc, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", "::"); +} + +// This test verifies that the allocator can step over prefixes +TEST_F(AllocEngine6Test, IterativeAllocatorPrefixIncrease) { + NakedAllocEngine::NakedIterativeAllocator alloc(Lease::TYPE_PD); + + // For /128 prefix, increasePrefix should work the same as addressIncrease + checkPrefixIncrease(alloc, "2001:db8::9", 128, "2001:db8::a"); + checkPrefixIncrease(alloc, "2001:db8::f", 128, "2001:db8::10"); + checkPrefixIncrease(alloc, "2001:db8::10", 128, "2001:db8::11"); + checkPrefixIncrease(alloc, "2001:db8::ff", 128, "2001:db8::100"); + checkPrefixIncrease(alloc, "2001:db8::ffff", 128, "2001:db8::1:0"); + checkPrefixIncrease(alloc, "::", 128, "::1"); + checkPrefixIncrease(alloc, "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff", 128, "::"); + + // Check that /64 prefixes can be generated + checkPrefixIncrease(alloc, "2001:db8::", 64, "2001:db8:0:1::"); + + // Check that prefix length not divisible by 8 are working + checkPrefixIncrease(alloc, "2001:db8::", 128, "2001:db8::1"); + checkPrefixIncrease(alloc, "2001:db8::", 127, "2001:db8::2"); + checkPrefixIncrease(alloc, "2001:db8::", 126, "2001:db8::4"); + checkPrefixIncrease(alloc, "2001:db8::", 125, "2001:db8::8"); + checkPrefixIncrease(alloc, "2001:db8::", 124, "2001:db8::10"); + checkPrefixIncrease(alloc, "2001:db8::", 123, "2001:db8::20"); + checkPrefixIncrease(alloc, "2001:db8::", 122, "2001:db8::40"); + checkPrefixIncrease(alloc, "2001:db8::", 121, "2001:db8::80"); + checkPrefixIncrease(alloc, "2001:db8::", 120, "2001:db8::100"); + + // These are not really useful cases, because there are bits set + // int the last (128 - prefix_len) bits. Nevertheless, it shows + // that the algorithm is working even in such cases + checkPrefixIncrease(alloc, "2001:db8::1", 128, "2001:db8::2"); + checkPrefixIncrease(alloc, "2001:db8::1", 127, "2001:db8::3"); + checkPrefixIncrease(alloc, "2001:db8::1", 126, "2001:db8::5"); + checkPrefixIncrease(alloc, "2001:db8::1", 125, "2001:db8::9"); + checkPrefixIncrease(alloc, "2001:db8::1", 124, "2001:db8::11"); + checkPrefixIncrease(alloc, "2001:db8::1", 123, "2001:db8::21"); + checkPrefixIncrease(alloc, "2001:db8::1", 122, "2001:db8::41"); + checkPrefixIncrease(alloc, "2001:db8::1", 121, "2001:db8::81"); + checkPrefixIncrease(alloc, "2001:db8::1", 120, "2001:db8::101"); + + // Let's try out couple real life scenarios + checkPrefixIncrease(alloc, "2001:db8:1:abcd::", 64, "2001:db8:1:abce::"); + checkPrefixIncrease(alloc, "2001:db8:1:abcd::", 60, "2001:db8:1:abdd::"); + checkPrefixIncrease(alloc, "2001:db8:1:abcd::", 56, "2001:db8:1:accd::"); + checkPrefixIncrease(alloc, "2001:db8:1:abcd::", 52, "2001:db8:1:bbcd::"); + + // And now let's try something over the top + checkPrefixIncrease(alloc, "::", 1, "8000::"); +} + +// This test verifies that the iterative allocator really walks over all addresses +// in all pools in specified subnet. It also must not pick the same address twice +// unless it runs out of pool space and must start over. +TEST_F(AllocEngine6Test, IterativeAllocator_manyPools6) { + NakedAllocEngine::IterativeAllocator alloc(Lease::TYPE_NA); + + // let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already. + for (int i = 2; i < 10; ++i) { + stringstream min, max; + + min << "2001:db8:1::" << hex << i*16 + 1; + max << "2001:db8:1::" << hex << i*16 + 9; + + Pool6Ptr pool(new Pool6(Lease::TYPE_NA, IOAddress(min.str()), + IOAddress(max.str()))); + subnet_->addPool(pool); + } + + int total = 17 + 8 * 9; // First pool (::10 - ::20) has 17 addresses in it, + // there are 8 extra pools with 9 addresses in each. + + // Let's keep picked addresses here and check their uniqueness. + std::set<IOAddress> generated_addrs; + int cnt = 0; + while (++cnt) { + IOAddress candidate = alloc.pickAddress(subnet_, cc_, + duid_, IOAddress("::")); + EXPECT_TRUE(subnet_->inPool(Lease::TYPE_NA, candidate)); + + // One way to easily verify that the iterative allocator really works is + // to uncomment the following line and observe its output that it + // covers all defined pools. + // cout << candidate.toText() << endl; + + if (generated_addrs.find(candidate) == generated_addrs.end()) { + // We haven't had this. + generated_addrs.insert(candidate); + } else { + // We have seen this address before. That should mean that we + // iterated over all addresses. + if (generated_addrs.size() == total) { + // We have exactly the number of address in all pools. + break; + } + ADD_FAILURE() << "Too many or not enough unique addresses generated."; + break; + } + + if ( cnt>total ) { + ADD_FAILURE() << "Too many unique addresses generated."; + break; + } + } +} + +// 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(AllocEngine::ALLOC_ITERATIVE, 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(AllocEngine::ALLOC_ITERATIVE, 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_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4)); + 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(), 0)); + lease->cltt_ = time(NULL) - 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", 2)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network", 2)); + EXPECT_EQ(1, getStatistics("v6-allocation-fail-subnet", 2)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", 2)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", 2)); +} + +// 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(AllocEngine::ALLOC_ITERATIVE, 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(), 0)); + lease->cltt_ = time(NULL) - 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_EQ(cumulative, + getStatistics("cumulative-assigned-nas", subnet_->getID())); + EXPECT_EQ(glbl_cumulative, getStatistics("cumulative-assigned-nas")); + 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(AllocEngine::ALLOC_ITERATIVE, 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(), 0)); + lease->cltt_ = time(NULL) - 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(AllocEngine::ALLOC_ITERATIVE, 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(), 0)); + lease->cltt_ = time(NULL) - 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(AllocEngine::ALLOC_ITERATIVE, 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(), 0)); + lease->cltt_ = time(NULL) - 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(AllocEngine::ALLOC_ITERATIVE, 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(), 0)); + lease->cltt_ = time(NULL) - 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 in REQUEST (actual allocation) +TEST_F(AllocEngine6Test, requestReuseExpiredLease6) { + boost::scoped_ptr<AllocEngine> engine; + ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 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_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4)); + 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(), + 0)); + int64_t other_cumulative = + getStatistics("cumulative-assigned-nas", other_subnetid); + + lease->cltt_ = time(NULL) - 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_EQ(other_cumulative, + getStatistics("cumulative-assigned-nas", other_subnetid)); + 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(), 128)); + + // Allocated 200 seconds ago - half of the lifetime. + time_t lease_cltt = time(NULL) - 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(), 128)); + + // Allocated 200 seconds ago - half of the lifetime. + time_t lease_cltt = time(NULL) - 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(), 128)); + + // Allocated 200 seconds ago - half of the lifetime. + time_t lease_cltt = time(NULL) - 200; + lease->cltt_ = lease_cltt; + + ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); + + AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 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, true); + 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 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(), 128)); + + // Allocated 200 seconds ago - half of the lifetime. + time_t lease_cltt = time(NULL) - 200; + lease->cltt_ = lease_cltt; + + ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); + + AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 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, true); + 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(), 128)); + + // Allocated 200 seconds ago - half of the lifetime. + time_t lease_cltt = time(NULL) - 200; + lease->cltt_ = lease_cltt; + + ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); + + AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 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, true); + 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(), 128)); + + // Allocated 200 seconds ago - half of the lifetime. + time_t lease_cltt = time(NULL) - 200; + lease->cltt_ = lease_cltt; + + ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); + + AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 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, true); + 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(), 128)); + + // Allocated 200 seconds ago - half of the lifetime. + time_t lease_cltt = time(NULL) - 200; + lease->cltt_ = lease_cltt; + + ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); + + AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 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, true); + 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_); +} + +// Checks if the lease lifetime is extended when the client sends the +// 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(), 128)); + + // Allocated 200 seconds ago - half of the lifetime. + time_t lease_cltt = time(NULL) - 200; + lease->cltt_ = lease_cltt; + + ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); + + AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 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, true); + 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(AllocEngine::ALLOC_ITERATIVE, 100, false); + + 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, 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(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // 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(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // 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_EQ(cumulative, + getStatistics("cumulative-assigned-nas", subnet_->getID())); + EXPECT_EQ(glbl_cumulative, getStatistics("cumulative-assigned-nas")); +} + +// 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(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // 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(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // 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_EQ(cumulative, + getStatistics("cumulative-assigned-nas", subnet_->getID())); + EXPECT_EQ(glbl_cumulative, getStatistics("cumulative-assigned-nas")); +} + +// 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(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // 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(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // 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_EQ(cumulative, + getStatistics("cumulative-assigned-nas", subnet_->getID())); + EXPECT_EQ(glbl_cumulative, getStatistics("cumulative-assigned-nas")); +} + +// 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(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // 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_EQ(cumulative, + getStatistics("cumulative-assigned-nas", subnet_->getID())); + EXPECT_EQ(glbl_cumulative, getStatistics("cumulative-assigned-nas")); +} + +// 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(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // 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_EQ(cumulative, + getStatistics("cumulative-assigned-nas", subnet_->getID())); + EXPECT_EQ(glbl_cumulative, getStatistics("cumulative-assigned-nas")); +} + +// 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(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // 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_EQ(cumulative, + getStatistics("cumulative-assigned-nas", subnet_->getID())); + EXPECT_EQ(glbl_cumulative, getStatistics("cumulative-assigned-nas")); +} + +// 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(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // 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_EQ(cumulative, + getStatistics("cumulative-assigned-nas", subnet_->getID())); + EXPECT_EQ(glbl_cumulative, getStatistics("cumulative-assigned-nas")); +} + +// 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(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // 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_EQ(cumulative, + getStatistics("cumulative-assigned-nas", subnet_->getID())); + EXPECT_EQ(glbl_cumulative, getStatistics("cumulative-assigned-nas")); +} + +// 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(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // 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(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // 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(AllocEngine::ALLOC_ITERATIVE, 100, true); + + // 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", 1)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network", 1)); + EXPECT_EQ(failure, getStatistics("v6-allocation-fail-subnet", 1)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", 1)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", 1)); + } 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(AllocEngine::ALLOC_ITERATIVE, 100, true); + + // 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", 1)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network", 1)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet", 1)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", 1)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", 1)); + + // 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", 1)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network", 1)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet", 1)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", 1)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", 1)); +} + +// Checks whether an address can be renewed (simple case, no reservation tricks) +TEST_F(AllocEngine6Test, addressRenewal) { + AllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 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, true); + 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(AllocEngine::ALLOC_ITERATIVE, 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, true); + 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(AllocEngine::ALLOC_ITERATIVE, 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(AllocEngine::ALLOC_ITERATIVE, 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(AllocEngine::ALLOC_ITERATIVE, 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, true); + 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(AllocEngine::ALLOC_ITERATIVE, 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, true); + 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(AllocEngine::ALLOC_ITERATIVE, 100, false); + + 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(AllocEngine::ALLOC_ITERATIVE, 100, false); + + 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(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // 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(AllocEngine::ALLOC_ITERATIVE, 100, false); + + // 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(AllocEngine::ALLOC_ITERATIVE, 0); + + // Remove the default PD pool. + subnet_->delPools(Lease::TYPE_PD); + + // Configure the PD pool with the prefix length of /64 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 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(AllocEngine::ALLOC_ITERATIVE, 0); + + // Configure 2001:db8::/32 subnet + subnet_.reset(new Subnet6(IOAddress("2001:db8::"), 32, 1, 2, 3, 4)); + + // 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(), 0)); + 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(AllocEngine::ALLOC_ITERATIVE, 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", 1)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-shared-network", 1)); + EXPECT_EQ(1, getStatistics("v6-allocation-fail-subnet", 1)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", 1)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", 1)); + + // This time, lets allow more attempts, and expect that the allocation will + // be successful. + AllocEngine engine2(AllocEngine::ALLOC_ITERATIVE, 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(AllocEngine::ALLOC_ITERATIVE, 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(AllocEngine::ALLOC_ITERATIVE, 100, true)); + 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(AllocEngine::ALLOC_ITERATIVE, + 100, true)); + 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_EQ(cumulative, + getStatistics("cumulative-assigned-nas", subnet_->getID())); + EXPECT_EQ(glbl_cumulative, getStatistics("cumulative-assigned-nas")); + 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(AllocEngine::ALLOC_ITERATIVE, + 100, true)); + 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(AllocEngine::ALLOC_ITERATIVE, 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_ = Subnet6Ptr(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4)); + 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(), + 0)); + lease->cltt_ = time(NULL) - 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_(AllocEngine::ALLOC_ITERATIVE, 0) { + + subnet1_.reset(new Subnet6(IOAddress("2001:db8:1::"), 56, 1, 2, 3, 4)); + subnet2_.reset(new Subnet6(IOAddress("2001:db8:2::"), 56, 1, 2, 3, 4)); + 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(), 0)); + lease->cltt_ = time(NULL) - 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(), 0)); + lease->cltt_ = time(NULL) - 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(), 128)); + 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", 3)); + EXPECT_EQ(1, getStatistics("v6-allocation-fail-shared-network", 3)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-subnet", 3)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-no-pools", 3)); + EXPECT_EQ(0, getStatistics("v6-allocation-fail-classes", 3)); +} + +// 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(AllocEngine::ALLOC_ITERATIVE, 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, true); + 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(AllocEngine::ALLOC_ITERATIVE, 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, true); + 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 global address reservation can get and +// renew a lease for an arbitrary address. +TEST_F(AllocEngine6Test, globalHostReservedAddress) { + boost::scoped_ptr<AllocEngine> engine; + ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 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 the fixed address. + Lease6Ptr lease; + ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx))); + ASSERT_TRUE(lease); + EXPECT_EQ("3001::1", 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::1"), 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, false); + 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(AllocEngine::ALLOC_ITERATIVE, 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, false); + 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(AllocEngine::ALLOC_ITERATIVE, 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, true); + 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, mixedHostReservedPrefix) { + boost::scoped_ptr<AllocEngine> engine; + ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 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, true); + 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(AllocEngine::ALLOC_ITERATIVE, 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, true); + 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(AllocEngine::ALLOC_ITERATIVE, 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, true); + 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_(AllocEngine::ALLOC_ITERATIVE, 100, true), duid1_(), duid2_(), + relay1_(), relay2_(), duid1_addr_("::"), duid2_addr_("::") { + duid1_.reset(new DUID(std::vector<uint8_t>(8, 0x84))); + duid2_.reset(new DUID(std::vector<uint8_t>(8, 0x74))); + + 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; + + duid1_addr_ = IOAddress("2001:db8:1::10"); + duid2_addr_ = IOAddress("2001:db8:1::11"); + + // Create the allocation engine, context and lease. + NakedAllocEngine engine(AllocEngine::ALLOC_ITERATIVE, 100, true); + } + + /// Configuration elements. These are initialized in the constructor + /// and are used throughout the tests. + NakedAllocEngine engine_; + DuidPtr duid1_; + DuidPtr duid2_; + Pkt6::RelayInfo relay1_; + Pkt6::RelayInfo relay2_; + 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\": { \"relays\": [ { \"hop\": 33, \"link\": \"2001:db8::1\"," + " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" } ] } }", + true + }, + { + "some original context, one relay", + "{\"foo\": 123}", + { relay1_ }, + "{ \"ISC\": { \"relays\": [ { \"hop\": 33, \"link\": \"2001:db8::1\"," + " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" } ] }," + " \"foo\": 123 }", + true + }, + { + "no original context, two relays", + "", + { relay1_, relay2_ }, + "{ \"ISC\": { \"relays\": [ { \"hop\": 33, \"link\": \"2001:db8::1\"," + " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" }," + " {\"hop\": 77, \"link\": \"2001:db8::3\", \"peer\": \"2001:db8::4\" } ] } }", + true + }, + { + "original relay context, no relay", + "{ \"ISC\": { \"relays\": [ { \"hop\": 33, \"link\": \"2001:db8::1\"," + " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" } ] } }", + {}, + "{ \"ISC\": { \"relays\": [ { \"hop\": 33, \"link\": \"2001:db8::1\"," + " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" } ] } }", + false + }, + { + "original relay context, different relay", + "{ \"ISC\": { \"relays\": [ { \"hop\": 33, \"link\": \"2001:db8::1\"," + " \"options\": \"0x00C800080102030405060708\", \"peer\": \"2001:db8::2\" } ] } }", + { relay2_ }, + "{ \"ISC\": { \"relays\": [ { \"hop\": 77, \"link\": \"2001:db8::3\"," + " \"peer\": \"2001:db8::4\" } ] } }", + 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. + ctx.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 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(). + bool ret; + ASSERT_NO_THROW_LOG(ret = engine_.callUpdateLease6ExtendedInfo(lease, ctx)); + 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\": { \"relays\": [ { \"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\": { \"relays\": [ { \"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 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 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(), 0)); + lease->cltt_ = time(NULL) - 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\": { \"relays\": [ { \"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(AllocEngine::ALLOC_ITERATIVE, 100))); + ASSERT_TRUE(engine); + + // Set the threshold to 25%. + subnet_->setCacheThreshold(.25); + + IOAddress addr("2001:db8:1::15"); + time_t now = time(NULL) - 100; // Allocated 100 seconds ago. + Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_, + 300, 400, subnet_->getID())); + lease->cltt_ = now; + ASSERT_FALSE(lease->expired()); + // Copy the lease, so as it can be compared with. + Lease6Ptr original_lease(new Lease6(*lease)); + ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); + + // Create a context for fake allocation.. + Pkt6Ptr query(new Pkt6(DHCPV6_SOLICIT, 1234)); + AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", true, + query); + ctx.currentIA().iaid_ = iaid_; + ctx.currentIA().addHint(addr); + + EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx))); + 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(AllocEngine::ALLOC_ITERATIVE, 100))); + ASSERT_TRUE(engine); + + // Set the threshold to 33%. + subnet_->setCacheThreshold(.33); + + IOAddress addr("2001:db8:1::15"); + time_t now = time(NULL) - 100; // Allocated 100 seconds ago. + Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_, + 300, 400, subnet_->getID())); + lease->cltt_ = now; + ASSERT_FALSE(lease->expired()); + // Copy the lease, so as it can be compared with. + Lease6Ptr original_lease(new Lease6(*lease)); + ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); + + // Create a context for 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(AllocEngine::ALLOC_ITERATIVE, 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(NULL) - 100; // Allocated 100 seconds ago. + Lease6Ptr lease(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid_, + 300, 400, subnet_->getID(), + HWAddrPtr(), prefixlen)); + lease->cltt_ = now; + ASSERT_FALSE(lease->expired()); + // 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(AllocEngine::ALLOC_ITERATIVE, 100))); + ASSERT_TRUE(engine); + + // Set the max age to 150. + subnet_->setCacheMaxAge(150); + + IOAddress addr("2001:db8:1::15"); + time_t now = time(NULL) - 100; // Allocated 100 seconds ago. + Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_, + 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(AllocEngine::ALLOC_ITERATIVE, 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(NULL) - 100; // Allocated 100 seconds ago. + Lease6Ptr lease(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid_, + 300, 400, subnet_->getID(), + HWAddrPtr(), prefixlen)); + lease->cltt_ = now; + ASSERT_FALSE(lease->expired()); + // 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(AllocEngine::ALLOC_ITERATIVE, 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(NULL) - 100; // Allocated 100 seconds ago. + Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_, + 300, 400, subnet_->getID())); + lease->cltt_ = now; + ASSERT_FALSE(lease->expired()); + // Copy the lease, so as it can be compared with. + Lease6Ptr original_lease(new Lease6(*lease)); + ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); + + // Create a context for 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(AllocEngine::ALLOC_ITERATIVE, 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(NULL) - 100; // Allocated 100 seconds ago. + Lease6Ptr lease(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid_, + 300, 400, subnet_->getID(), + HWAddrPtr(), prefixlen)); + lease->cltt_ = now; + ASSERT_FALSE(lease->expired()); + // 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(AllocEngine::ALLOC_ITERATIVE, 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(NULL) - 100; // Allocated 100 seconds ago. + Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_, + 300, 400, subnet_->getID())); + lease->cltt_ = now; + ASSERT_FALSE(lease->expired()); + ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); + + // Create a context for request. + Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234)); + AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, + query); + ctx.currentIA().iaid_ = iaid_; + ctx.currentIA().addHint(addr); + + EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx))); + 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(AllocEngine::ALLOC_ITERATIVE, 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(NULL) - 100; // Allocated 100 seconds ago. + Lease6Ptr lease(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid_, + 300, 400, subnet_->getID(), + HWAddrPtr(), prefixlen)); + lease->cltt_ = now; + ASSERT_FALSE(lease->expired()); + ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); + + // Create a context for renew. + Pkt6Ptr query(new Pkt6(DHCPV6_RENEW, 1234)); + AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, + query); + ctx.currentIA().type_ = Lease::TYPE_PD; + ctx.currentIA().iaid_ = iaid_; + ctx.currentIA().addHint(prefix, prefixlen); + + EXPECT_NO_THROW(lease = expectOneLease(engine->renewLeases6(ctx))); + 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(AllocEngine::ALLOC_ITERATIVE, 100))); + ASSERT_TRUE(engine); + + // Set the threshold to 25%. + subnet_->setCacheThreshold(.25); + + // Set the max age to 50. + subnet_->setCacheMaxAge(50); + + IOAddress addr("2001:db8:1::15"); + time_t now = time(NULL) - 100; // Allocated 100 seconds ago. + Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_, + 300, 400, subnet_->getID())); + lease->cltt_ = now; + ASSERT_FALSE(lease->expired()); + ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); + + // Create a context for request. + Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234)); + AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, + query); + ctx.currentIA().iaid_ = iaid_; + ctx.currentIA().addHint(addr); + + EXPECT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx))); + 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(AllocEngine::ALLOC_ITERATIVE, 100))); + ASSERT_TRUE(engine); + + // Set the threshold to 25%. + subnet_->setCacheThreshold(.25); + + // Set the max age to 50. + subnet_->setCacheMaxAge(50); + + IOAddress prefix("2001:db8:1:2::"); + uint8_t prefixlen = 80; + time_t now = time(NULL) - 100; // Allocated 100 seconds ago. + 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(AllocEngine::ALLOC_ITERATIVE, 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(NULL) - 100; // Allocated 100 seconds ago. + Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_, + 300, 400, subnet_->getID())); + lease->cltt_ = now; + ASSERT_FALSE(lease->expired()); + ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); + + // Create a context for 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(AllocEngine::ALLOC_ITERATIVE, 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(NULL) - 100; // Allocated 100 seconds ago. + Lease6Ptr lease(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid_, + 300, 400, subnet_->getID(), + HWAddrPtr(), prefixlen)); + lease->cltt_ = now; + ASSERT_FALSE(lease->expired()); + ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); + + // Create a context for renew. + Pkt6Ptr query(new Pkt6(DHCPV6_RENEW, 1234)); + AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, + query); + ctx.currentIA().type_ = Lease::TYPE_PD; + ctx.currentIA().iaid_ = iaid_; + ctx.currentIA().addHint(prefix, prefixlen); + + EXPECT_NO_THROW(lease = expectOneLease(engine->renewLeases6(ctx))); + 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(AllocEngine::ALLOC_ITERATIVE, 100))); + ASSERT_TRUE(engine); + + // Set the threshold to 25%. + subnet_->setCacheThreshold(.25); + + IOAddress addr("2001:db8:1::15"); + time_t now = time(NULL) - 100; // Allocated 100 seconds ago. + Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_, + 300, 400, subnet_->getID())); + lease->cltt_ = now; + ASSERT_FALSE(lease->expired()); + 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(AllocEngine::ALLOC_ITERATIVE, 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(NULL) - 100; // Allocated 100 seconds ago. + Lease6Ptr lease(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid_, + 300, 400, subnet_->getID(), + HWAddrPtr(), prefixlen)); + lease->cltt_ = now; + ASSERT_FALSE(lease->expired()); + ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); + + // Create a context for renew. + Pkt6Ptr query(new Pkt6(DHCPV6_RENEW, 1234)); + AllocEngine::ClientContext6 ctx(subnet_, duid_, 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(AllocEngine::ALLOC_ITERATIVE, 100))); + ASSERT_TRUE(engine); + + // Set the threshold to 25%. + subnet_->setCacheThreshold(.25); + + IOAddress addr("2001:db8:1::15"); + time_t now = time(NULL) - 100; // Allocated 100 seconds ago. + Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_, + 300, 400, subnet_->getID())); + lease->cltt_ = now; + ASSERT_FALSE(lease->expired()); + 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(AllocEngine::ALLOC_ITERATIVE, 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(NULL) - 100; // Allocated 100 seconds ago. + Lease6Ptr lease(new Lease6(Lease::TYPE_PD, prefix, duid_, iaid_, + 300, 400, subnet_->getID(), + HWAddrPtr(), prefixlen)); + lease->cltt_ = now; + ASSERT_FALSE(lease->expired()); + ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); + + // Create a context for renew. + Pkt6Ptr query(new Pkt6(DHCPV6_RENEW, 1234)); + AllocEngine::ClientContext6 ctx(subnet_, duid_, false, 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(AllocEngine::ALLOC_ITERATIVE, 100))); + ASSERT_TRUE(engine); + + // Set the threshold to 25%. + subnet_->setCacheThreshold(.25); + + IOAddress addr("2001:db8:1::15"); + time_t now = time(NULL) - 100; // Allocated 100 seconds ago. + Lease6Ptr lease(new Lease6(Lease::TYPE_NA, addr, duid_, iaid_, + 300, 400, subnet_->getID(), + 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(AllocEngine::ALLOC_ITERATIVE, 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(NULL) - 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(AllocEngine::ALLOC_ITERATIVE, 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 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 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(AllocEngine::ALLOC_ITERATIVE, 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(); + + // Update the subnet's triplet to something more useful. + subnet_->setPreferred(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_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_->getPreferred().getMax() + }, + { + "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 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 class_name : scenario.classes_) { + ctx.query_->addClass(class_name); + } + + // Add hint + ctx.currentIA().iaid_ = iaid_; + + // prefix,prefixlen, preferred, preferred + 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 |