// Copyright (C) 2015-2023 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined HAVE_MYSQL #include #endif #if defined HAVE_PGSQL #include #endif #include using namespace std; using namespace isc::hooks; using namespace isc::asiolink; using namespace isc::data; using namespace isc::stats; using namespace isc::util; namespace isc { namespace dhcp { namespace test { // This test checks if the v4 Allocation Engine can be instantiated, parses // parameters string and allocators are created. TEST_F(AllocEngine4Test, constructor) { boost::scoped_ptr x; // Create V4 (ipv6=false) Allocation Engine that will try at most // 100 attempts to pick up a lease ASSERT_NO_THROW(x.reset(new AllocEngine(100))); } // This test checks if two simple IPv4 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(AllocEngine4Test, simpleAlloc4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); // Assigned addresses should be zero. EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID())); // Get the cumulative count of assigned addresses. int64_t cumulative = getStatistics("cumulative-assigned-addresses", subnet_->getID()); int64_t glbl_cumulative = getStatistics("cumulative-assigned-addresses"); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, true, "somehost.example.com.", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); Lease4Ptr lease = engine->allocateLease4(ctx); // The new lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // Check that we got a lease ASSERT_TRUE(lease); // Do all checks on the lease checkLease4(lease); // Check that the lease is indeed in LeaseMgr Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); // Now check that the lease in LeaseMgr has the same parameters detailCompareLease(lease, from_mgr); // Assigned addresses should have incremented. EXPECT_TRUE(testStatistics("assigned-addresses", 1, subnet_->getID())); cumulative += 1; EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", cumulative, subnet_->getID())); glbl_cumulative += 1; EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", glbl_cumulative)); // Second allocation starts here. uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe }; HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER)); AllocEngine::ClientContext4 ctx2(subnet_, ClientIdPtr(), hwaddr2, IOAddress("0.0.0.0"), false, true, "anotherhost.example.com.", false); ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1234)); // Set the next step to non-default value to verify that it doesn't // affect the allocation. ctx2.callout_handle_ = HooksManager::createCalloutHandle(); ctx2.callout_handle_->setStatus(CalloutHandle::NEXT_STEP_SKIP); // Set the last allocated to the beginning of the pool. The allocation // engine should detect that the first address is already allocated and // assign the first available one. auto allocation_state = boost::dynamic_pointer_cast(pool_->getAllocationState()); allocation_state->resetLastAllocated(); lease = engine->allocateLease4(ctx2); // The new lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx2.old_lease_); // Check that we got a lease ASSERT_TRUE(lease); // Check that the lease is indeed in LeaseMgr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); // Now check that the lease in LeaseMgr has the same parameters detailCompareLease(lease, from_mgr); // Assigned addresses should have incremented. EXPECT_TRUE(testStatistics("assigned-addresses", 2, subnet_->getID())); cumulative += 1; EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", cumulative, subnet_->getID())); glbl_cumulative += 1; EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", glbl_cumulative)); } // This test checks that simple allocation uses the default valid lifetime. TEST_F(AllocEngine4Test, defaultAlloc4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, true, "somehost.example.com.", false); subnet_->setValid(Triplet(1, 3, 5)); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); Lease4Ptr lease = engine->allocateLease4(ctx); // The new lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // Check that we got a lease ASSERT_TRUE(lease); // Do all checks on the lease checkLease4(lease); // Check the valid lifetime has the default. EXPECT_EQ(subnet_->getValid(), lease->valid_lft_); // Check that the lease is indeed in LeaseMgr Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); // Now check that the lease in LeaseMgr has the same parameters detailCompareLease(lease, from_mgr); } // This test checks that simple allocation uses the specified valid lifetime. TEST_F(AllocEngine4Test, hintAlloc4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, true, "somehost.example.com.", false); subnet_->setValid(Triplet(1, 3, 5)); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); // Specify the valid lifetime we want. OptionUint32Ptr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME, 4)); ctx.query_->addOption(opt); Lease4Ptr lease = engine->allocateLease4(ctx); // The new lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // Check that we got a lease ASSERT_TRUE(lease); // Do all checks on the lease checkLease4(lease); // Check the valid lifetime has the wanted value. EXPECT_EQ(opt->getValue(), lease->valid_lft_); // Check that the lease is indeed in LeaseMgr Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); // Now check that the lease in LeaseMgr has the same parameters detailCompareLease(lease, from_mgr); } // This test checks that simple allocation uses the min valid lifetime. TEST_F(AllocEngine4Test, minAlloc4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, true, "somehost.example.com.", false); subnet_->setValid(Triplet(2, 3, 5)); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); // Specify the valid lifetime we want, as it is lower than the min value // we'll get this min value instead. OptionUint32Ptr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME, 1)); ctx.query_->addOption(opt); Lease4Ptr lease = engine->allocateLease4(ctx); // The new lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // Check that we got a lease ASSERT_TRUE(lease); // Do all checks on the lease checkLease4(lease); // Check the valid lifetime has the wanted value. EXPECT_EQ(subnet_->getValid().getMin(), lease->valid_lft_); // Check that the lease is indeed in LeaseMgr Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); // Now check that the lease in LeaseMgr has the same parameters detailCompareLease(lease, from_mgr); } // This test checks that simple allocation uses the max valid lifetime. TEST_F(AllocEngine4Test, maxAlloc4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, true, "somehost.example.com.", false); subnet_->setValid(Triplet(1, 3, 5)); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); // Specify the valid lifetime we want, as it is greater than the max value // we'll get this max value instead. OptionUint32Ptr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME, 6)); ctx.query_->addOption(opt); Lease4Ptr lease = engine->allocateLease4(ctx); // The new lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // Check that we got a lease ASSERT_TRUE(lease); // Do all checks on the lease checkLease4(lease); // Check the valid lifetime has the wanted value. EXPECT_EQ(subnet_->getValid().getMax(), lease->valid_lft_); // Check that the lease is indeed in LeaseMgr Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); // Now check that the lease in LeaseMgr has the same parameters detailCompareLease(lease, from_mgr); } // This test checks that simple allocation handles BOOTP queries. TEST_F(AllocEngine4Test, bootpAlloc4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, true, "somehost.example.com.", false); subnet_->setValid(Triplet(1, 3, 5)); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); // Make the query a BOOTP one. ctx.query_->addClass("BOOTP"); Lease4Ptr lease = engine->allocateLease4(ctx); // The new lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // Check that we got a lease ASSERT_TRUE(lease); // Check that is belongs to the right subnet and client. EXPECT_EQ(lease->subnet_id_, subnet_->getID()); EXPECT_TRUE(subnet_->inRange(lease->addr_)); EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease->addr_)); ASSERT_TRUE(lease->client_id_); EXPECT_TRUE(*lease->client_id_ == *clientid_); ASSERT_TRUE(lease->hwaddr_); EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_); // Check the valid lifetime is infinite. uint32_t infinity_lft = Lease::INFINITY_LFT; EXPECT_EQ(infinity_lft, lease->valid_lft_); // Check that the lease is indeed in LeaseMgr Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); // Now check that the lease in LeaseMgr has the same parameters detailCompareLease(lease, from_mgr); } // This test checks if the fake allocation (for DHCPDISCOVER) can succeed TEST_F(AllocEngine4Test, fakeAlloc4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); // Assigned addresses should be zero. EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID())); // Get the cumulative count of assigned addresses. int64_t cumulative = getStatistics("cumulative-assigned-addresses", subnet_->getID()); int64_t glbl_cumulative = getStatistics("cumulative-assigned-addresses"); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, true, "host.example.com.", true); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); Lease4Ptr lease = engine->allocateLease4(ctx); // The new lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // Check that we got a lease ASSERT_TRUE(lease); // Do all checks on the lease checkLease4(lease); // Check that the lease is NOT in LeaseMgr Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_FALSE(from_mgr); // Assigned addresses should still be zero. EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID())); EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", cumulative, subnet_->getID())); EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", glbl_cumulative)); } // This test checks if the allocation with a hint that is valid (in range, // in pool and free) can succeed TEST_F(AllocEngine4Test, allocWithValidHint4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("192.0.2.105"), true, true, "host.example.com.", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); Lease4Ptr lease = engine->allocateLease4(ctx); // Check that we got a lease ASSERT_TRUE(lease); // We have allocated the new lease, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // We should get what we asked for EXPECT_EQ(lease->addr_.toText(), "192.0.2.105"); // Do all checks on the lease checkLease4(lease); // Check that the lease is indeed in LeaseMgr Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); // Now check that the lease in LeaseMgr has the same parameters detailCompareLease(lease, from_mgr); } // This test checks if the allocation with a hint that is in range, // in pool, but is currently used can succeed TEST_F(AllocEngine4Test, allocWithUsedHint4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); // Let's create a lease and put it in the LeaseMgr uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe }; HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER)); uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 }; time_t now = time(NULL); Lease4Ptr used(new Lease4(IOAddress("192.0.2.106"), hwaddr2, clientid2, sizeof(clientid2), 1, now, subnet_->getID())); ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used)); // Another client comes in and request an address that is in pool, but // unfortunately it is used already. The same address must not be allocated // twice. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("192.0.2.106"), false, false, "", true); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); Lease4Ptr lease = engine->allocateLease4(ctx); // New lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // Check that we got a lease ASSERT_TRUE(lease); // Allocated address must be different EXPECT_NE(used->addr_, lease->addr_); // We should NOT get what we asked for, because it is used already EXPECT_NE("192.0.2.106", lease->addr_.toText()); // Do all checks on the lease checkLease4(lease); // The lease should not be in the LeaseMgr because it was a failed allocation. Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_FALSE(from_mgr); } // This test checks if an allocation with a hint that is out of the blue // can succeed. The invalid hint should be ignored completely. TEST_F(AllocEngine4Test, allocBogusHint4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); // Client would like to get a 10.1.1.1 lease, which does not belong to any // supported lease. Allocation engine should ignore it and carry on // with the normal allocation AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("10.1.1.1"), false, false, "", true); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); Lease4Ptr lease = engine->allocateLease4(ctx); // Check that we got a lease ASSERT_TRUE(lease); // We have allocated a new lease, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // We should NOT get what we asked for, because it is used already EXPECT_NE("10.1.1.1", lease->addr_.toText()); // Do all checks on the lease checkLease4(lease); // Check that the lease is not in the LeaseMgr as it is a fake allocation. Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); EXPECT_FALSE(from_mgr); } // This test checks that NULL values are handled properly TEST_F(AllocEngine4Test, allocateLease4Nulls) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); // Allocations without subnet are not allowed AllocEngine::ClientContext4 ctx1(Subnet4Ptr(), clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "", false); ctx1.query_.reset(new Pkt4(DHCPREQUEST, 1234)); Lease4Ptr lease = engine->allocateLease4(ctx1); ASSERT_FALSE(lease); EXPECT_EQ(0, getStatistics("v4-allocation-fail")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes")); EXPECT_EQ(0, getStatistics("v4-allocation-fail", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID())); // Allocations without HW address are not allowed AllocEngine::ClientContext4 ctx2(subnet_, clientid_, HWAddrPtr(), IOAddress("0.0.0.0"), false, false, "", false); ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1234)); lease = engine->allocateLease4(ctx2); ASSERT_FALSE(lease); ASSERT_FALSE(ctx2.old_lease_); EXPECT_EQ(0, getStatistics("v4-allocation-fail")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes")); EXPECT_EQ(0, getStatistics("v4-allocation-fail", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID())); // Allocations without client-id are allowed clientid_.reset(); AllocEngine::ClientContext4 ctx3(subnet_, ClientIdPtr(), hwaddr_, IOAddress("0.0.0.0"), false, false, "", false); ctx3.query_.reset(new Pkt4(DHCPREQUEST, 1234)); lease = engine->allocateLease4(ctx3); // Check that we got a lease ASSERT_TRUE(lease); // New lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx3.old_lease_); // Do all checks on the lease checkLease4(lease); // Check that the lease is indeed in LeaseMgr Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); // Now check that the lease in LeaseMgr has the same parameters detailCompareLease(lease, from_mgr); } // This test checks if a returning client can renew an // an existing lease and assigned-leases increments accordingly TEST_F(AllocEngine4Test, simpleRenew4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID())); int64_t cumulative = getStatistics("cumulative-assigned-addresses", subnet_->getID()); int64_t glbl_cumulative = getStatistics("cumulative-assigned-addresses"); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, true, "somehost.example.com.", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); Lease4Ptr lease = engine->allocateLease4(ctx); // Check that we got a lease and it's sane ASSERT_TRUE(lease); checkLease4(lease); // The new lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // We should have incremented assigned-addresses EXPECT_TRUE(testStatistics("assigned-addresses", 1, subnet_->getID())); cumulative += 1; EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", cumulative, subnet_->getID())); glbl_cumulative += 1; EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", glbl_cumulative)); // Do it again, this should amount to the renew of an existing lease Lease4Ptr lease2 = engine->allocateLease4(ctx); // Check that we got a lease and it's sane ASSERT_TRUE(lease2); checkLease4(lease2); // Lease already existed, so old_lease should be set. EXPECT_TRUE(ctx.old_lease_); // Should NOT have bumped assigned-addresses EXPECT_TRUE(testStatistics("assigned-addresses", 1, subnet_->getID())); EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", cumulative, subnet_->getID())); EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", glbl_cumulative)); } // This test checks simple renewal uses the default valid lifetime. TEST_F(AllocEngine4Test, defaultRenew4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, true, "somehost.example.com.", false); subnet_->setValid(Triplet(1, 3, 5)); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); Lease4Ptr lease = engine->allocateLease4(ctx); // Check that we got a lease and it's sane ASSERT_TRUE(lease); checkLease4(lease); // Check the valid lifetime has the default. EXPECT_EQ(subnet_->getValid(), lease->valid_lft_); // The new lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // Do it again, this should amount to the renew of an existing lease Lease4Ptr lease2 = engine->allocateLease4(ctx); // Check that we got a lease and it's sane ASSERT_TRUE(lease2); checkLease4(lease2); // Lease already existed, so old_lease should be set. EXPECT_TRUE(ctx.old_lease_); // Check the renewed valid lifetime has the default. EXPECT_EQ(subnet_->getValid(), lease2->valid_lft_); } // This test checks simple renewal uses the specified valid lifetime. TEST_F(AllocEngine4Test, hintRenew4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, true, "somehost.example.com.", false); subnet_->setValid(Triplet(1, 3, 5)); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); // Specify the valid lifetime we want. OptionUint32Ptr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME, 4)); ctx.query_->addOption(opt); Lease4Ptr lease = engine->allocateLease4(ctx); // Check that we got a lease and it's sane ASSERT_TRUE(lease); checkLease4(lease); // Check the valid lifetime has the wanted value. EXPECT_EQ(opt->getValue(), lease->valid_lft_); // The new lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // Do it again, this should amount to the renew of an existing lease Lease4Ptr lease2 = engine->allocateLease4(ctx); // Check that we got a lease and it's sane ASSERT_TRUE(lease2); checkLease4(lease2); // Lease already existed, so old_lease should be set. EXPECT_TRUE(ctx.old_lease_); // Check the renewed valid lifetime has the wanted value. EXPECT_EQ(opt->getValue(), lease2->valid_lft_); } // This test checks simple renewal uses the min valid lifetime. TEST_F(AllocEngine4Test, minRenew4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, true, "somehost.example.com.", false); subnet_->setValid(Triplet(2, 3, 5)); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); // Specify the valid lifetime we want, as it is lower than the min value // we'll get this min value instead. OptionUint32Ptr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME, 1)); ctx.query_->addOption(opt); Lease4Ptr lease = engine->allocateLease4(ctx); // Check that we got a lease and it's sane ASSERT_TRUE(lease); checkLease4(lease); // Check the valid lifetime has the min value. EXPECT_EQ(subnet_->getValid().getMin(), lease->valid_lft_); // The new lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // Do it again, this should amount to the renew of an existing lease Lease4Ptr lease2 = engine->allocateLease4(ctx); // Check that we got a lease and it's sane ASSERT_TRUE(lease2); checkLease4(lease2); // Lease already existed, so old_lease should be set. EXPECT_TRUE(ctx.old_lease_); // Check the renewed valid lifetime has the min value. EXPECT_EQ(subnet_->getValid().getMin(), lease2->valid_lft_); } // This test checks simple renewal uses the max valid lifetime. TEST_F(AllocEngine4Test, maxRenew4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, true, "somehost.example.com.", false); subnet_->setValid(Triplet(1, 3, 5)); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); // Specify the valid lifetime we want, as it is greater than the max value // we'll get this max value instead. OptionUint32Ptr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME, 6)); ctx.query_->addOption(opt); Lease4Ptr lease = engine->allocateLease4(ctx); // Check that we got a lease and it's sane ASSERT_TRUE(lease); checkLease4(lease); // Check the valid lifetime has the max value. EXPECT_EQ(subnet_->getValid().getMax(), lease->valid_lft_); // The new lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // Do it again, this should amount to the renew of an existing lease Lease4Ptr lease2 = engine->allocateLease4(ctx); // Check that we got a lease and it's sane ASSERT_TRUE(lease2); checkLease4(lease2); // Lease already existed, so old_lease should be set. EXPECT_TRUE(ctx.old_lease_); // Check the renewed valid lifetime has the max value. EXPECT_EQ(subnet_->getValid().getMax(), lease2->valid_lft_); } // This test checks simple renewal handles BOOTP queries. TEST_F(AllocEngine4Test, bootpRenew4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, true, "somehost.example.com.", false); subnet_->setValid(Triplet(1, 3, 5)); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); // Make the query a BOOTP one. ctx.query_->addClass("BOOTP"); Lease4Ptr lease = engine->allocateLease4(ctx); // Check that we got a lease. ASSERT_TRUE(lease); // Check that is belongs to the right subnet and client. EXPECT_EQ(lease->subnet_id_, subnet_->getID()); EXPECT_TRUE(subnet_->inRange(lease->addr_)); EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease->addr_)); ASSERT_TRUE(lease->client_id_); EXPECT_TRUE(*lease->client_id_ == *clientid_); ASSERT_TRUE(lease->hwaddr_); EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_); // Check the valid lifetime is infinite. uint32_t infinity_lft = Lease::INFINITY_LFT; EXPECT_EQ(infinity_lft, lease->valid_lft_); // The new lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // Do it again, this should amount to the renew of an existing lease Lease4Ptr lease2 = engine->allocateLease4(ctx); // Check that we got a lease. ASSERT_TRUE(lease2); // Check that is belongs to the right subnet and client. EXPECT_EQ(lease2->subnet_id_, subnet_->getID()); EXPECT_TRUE(subnet_->inRange(lease2->addr_)); EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease2->addr_)); ASSERT_TRUE(lease2->client_id_); EXPECT_TRUE(*lease2->client_id_ == *clientid_); ASSERT_TRUE(lease2->hwaddr_); EXPECT_TRUE(*lease2->hwaddr_ == *hwaddr_); // Lease already existed, so old_lease should be set. EXPECT_TRUE(ctx.old_lease_); // Check the renewed valid lifetime has the max value. EXPECT_EQ(infinity_lft, lease2->valid_lft_); } // This test checks if really small pools are working TEST_F(AllocEngine4Test, smallPool4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); IOAddress addr("192.0.2.17"); CfgMgr& cfg_mgr = CfgMgr::instance(); // Get rid of the default subnet configuration. cfg_mgr.clear(); // Create configuration similar to other tests, but with a single address pool subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10)); pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address subnet_->addPool(pool_); cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "host.example.com.", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); Lease4Ptr lease = engine->allocateLease4(ctx); // Check that we got that single lease ASSERT_TRUE(lease); // We have allocated new lease, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); EXPECT_EQ("192.0.2.17", lease->addr_.toText()); // Do all checks on the lease checkLease4(lease); // Check that the lease is indeed in LeaseMgr Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); // Now check that the lease in LeaseMgr has the same parameters detailCompareLease(lease, from_mgr); } // This test checks if all addresses in a pool are currently used, the attempt // to find out a new lease fails. TEST_F(AllocEngine4Test, outOfAddresses4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); IOAddress addr("192.0.2.17"); CfgMgr& cfg_mgr = CfgMgr::instance(); // Get rid of the default test configuration. cfg_mgr.clear(); // Create configuration similar to other tests, but with a single address pool subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10)); pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address subnet_->addPool(pool_); cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_); // Just a different hw/client-id for the second client uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe }; HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER)); uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 }; time_t now = time(NULL); Lease4Ptr lease(new Lease4(addr, hwaddr2, clientid2, sizeof(clientid2), 501, now, subnet_->getID())); 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 AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "host.example.com.", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); Lease4Ptr lease2 = engine->allocateLease4(ctx); ASSERT_FALSE(lease2); ASSERT_FALSE(ctx.old_lease_); EXPECT_EQ(1, getStatistics("v4-allocation-fail")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network")); EXPECT_EQ(1, getStatistics("v4-allocation-fail-subnet")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes")); EXPECT_EQ(1, getStatistics("v4-allocation-fail", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID())); EXPECT_EQ(1, getStatistics("v4-allocation-fail-subnet", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID())); } /// @brief This test class is dedicated to testing shared networks /// /// It uses one common configuration: /// 1 shared network with 2 subnets: /// - 192.0.2.0/24 subnet with a small pool of single address: 192.0.2.17 /// - 10.1.2.0/24 subnet with pool with 96 addresses. class SharedNetworkAlloc4Test : public AllocEngine4Test { public: /// @brief Initializes configuration (2 subnets, 1 shared network) SharedNetworkAlloc4Test() : engine_(0) { // Create two subnets, each with a single address pool. The first subnet // has only one address in its address pool to make it easier to simulate // address exhaustion. subnet1_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10)); subnet2_ = Subnet4::create(IOAddress("10.1.2.0"), 24, 1, 2, 3, SubnetID(20)); pool1_.reset(new Pool4(IOAddress("192.0.2.17"), IOAddress("192.0.2.17"))); pool2_.reset(new Pool4(IOAddress("10.1.2.5"), IOAddress("10.1.2.100"))); subnet1_->addPool(pool1_); subnet2_->addPool(pool2_); // Both subnets belong to the same network so they can be used // interchangeably. network_.reset(new SharedNetwork4("test_network")); network_->add(subnet1_); network_->add(subnet2_); std::vector hwaddr_vec = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe }; hwaddr2_.reset(new HWAddr(hwaddr_vec, HTYPE_ETHER)); } /// @brief Inserts a new lease for specified address /// /// Creates a new lease for specified address and subnet-id and inserts /// it into database. This is not particularly fancy method, it is used /// just to mark existing addresses as used. It uses hwaddr2_ to allocate /// the lease. /// /// @param addr text representation of the address /// @param subnet_id ID of the subnet /// @param return pointer to the lease Lease4Ptr insertLease(std::string addr, SubnetID subnet_id) { Lease4Ptr lease(new Lease4(IOAddress(addr), hwaddr2_, ClientIdPtr(), 501, time(NULL), subnet_id)); lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago if (!LeaseMgrFactory::instance().addLease(lease)) { ADD_FAILURE() << "Attempt to add a lease for IP " << addr << " in subnet " << subnet_id << " failed"; } return (lease); } /// Convenience pointers to configuration elements. These are initialized /// in the constructor and are used throughout the tests. AllocEngine engine_; Subnet4Ptr subnet1_; Subnet4Ptr subnet2_; Pool4Ptr pool1_; Pool4Ptr pool2_; SharedNetwork4Ptr network_; HWAddrPtr hwaddr2_; // Note there's hwaddr_ already defined in base class. }; // 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(SharedNetworkAlloc4Test, discoverSharedNetworkSimple) { // 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. AllocEngine::ClientContext4 ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(), false, false, "host.example.com.", true); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); Lease4Ptr lease = engine_.allocateLease4(ctx); // The allocation engine should have assigned an address from the first // subnet. ASSERT_TRUE(lease); EXPECT_EQ("192.0.2.17", lease->addr_.toText()); // Make sure the lease is not in the lease mgr (this is only // discover). ASSERT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_)); } // This test verifies that the server will pick a second subnet out of two // shared subnets if there is a hint for the second subnet. TEST_F(SharedNetworkAlloc4Test, discoverSharedNetworkHint) { // 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. AllocEngine::ClientContext4 ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress("10.1.2.25"), false, false, "host.example.com.", true); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); Lease4Ptr lease = engine_.allocateLease4(ctx); // The allocation engine should have assigned an address from the second // subnet, because that's what the hint requested. ASSERT_TRUE(lease); EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->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(SharedNetworkAlloc4Test, discoverSharedNetwork) { // Create a lease for a single address in the first address pool. The // pool is now exhausted. Lease4Ptr lease = insertLease("192.0.2.17", subnet1_->getID()); // 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. AllocEngine::ClientContext4 ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(), false, false, "host.example.com.", true); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); Lease4Ptr lease2 = engine_.allocateLease4(ctx); // The allocation engine should have assigned an address from the second // subnet. We could guess that this is 10.1.2.5, being the first address // in the address pool, but to make the test more generic, we merely // verify that the address is in the given address pool. ASSERT_TRUE(lease2); EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease2->addr_)); // The client should also be offered a lease when it specifies a hint // that doesn't match the subnet from which the lease is offered. The // engine should check alternative subnets to match the hint to // a subnet. The requested lease is available, so it should be offered. ctx.subnet_ = subnet1_; ctx.requested_address_ = IOAddress("10.1.2.25"); lease2 = engine_.allocateLease4(ctx); ASSERT_TRUE(lease2); EXPECT_EQ("10.1.2.25", lease2->addr_.toText()); // The returning client (the one that has a lease) should also be able // to renew its lease regardless of a subnet it begins with. So, it has // an address assigned from subnet1, but we use subnet2 as a selected // subnet. AllocEngine::ClientContext4 ctx2(subnet2_, ClientIdPtr(), hwaddr2_, IOAddress("0.0.0.0"), false, false, "host.example.com.", true); ctx2.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); lease2 = engine_.allocateLease4(ctx2); // The existing lease should be returned. ASSERT_TRUE(lease2); EXPECT_EQ("192.0.2.17", lease2->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 is exhausted. TEST_F(SharedNetworkAlloc4Test, discoverSharedNetworkClassification) { // Try to offer address from subnet1. There is one address available // so it should be offered. AllocEngine::ClientContext4 ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(), false, false, "host.example.com.", true); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); Lease4Ptr lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_TRUE(subnet1_->inPool(Lease::TYPE_V4, 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. ctx.subnet_ = subnet1_; lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_)); // Create reservation for the client in subnet1. Because this subnet is // not allowed for the client the client should still be offered a // lease from subnet2. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet1_->getID(), SUBNET_ID_UNUSED, IOAddress("192.0.2.17"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); AllocEngine::findReservation(ctx); ctx.subnet_ = subnet1_; lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_)); // Assign cable-modem class and try again. This time, we should // offer an address from the subnet1. ctx.query_->addClass(ClientClass("cable-modem")); ctx.subnet_ = subnet1_; lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("192.0.2.17", 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(SharedNetworkAlloc4Test, discoverSharedNetworkPoolClassification) { // Try to offer address from subnet1. There is one address available // so it should be offered. AllocEngine::ClientContext4 ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(), false, false, "host.example.com.", true); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); Lease4Ptr lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_TRUE(subnet1_->inPool(Lease::TYPE_V4, 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. ctx.subnet_ = subnet1_; lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_)); // Assign cable-modem class and try again. This time, we should // offer an address from the pool1. ctx.query_->addClass(ClientClass("cable-modem")); // Restrict access to pool2 for this client, to make sure that the // server doesn't accidentally get an address from this pool. pool2_->allowClientClass("telephone"); ctx.subnet_ = subnet1_; lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("192.0.2.17", lease->addr_.toText()); } // Test that global reservations within shared network take precedence over the // existing leases regardless in which subnet belonging to a shared network // reservations belong. TEST_F(SharedNetworkAlloc4Test, discoverSharedNetworkReservationsGlobal) { EXPECT_FALSE(HostMgr::instance().getDisableSingleQuery()); // Create reservation for the client. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, SUBNET_ID_GLOBAL, SUBNET_ID_UNUSED, IOAddress("10.1.2.105"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); subnet1_->setReservationsGlobal(true); subnet1_->setReservationsInSubnet(true); subnet2_->setReservationsGlobal(true); subnet2_->setReservationsInSubnet(true); // Start allocation from subnet1. The engine should determine that the // client has global reservations within subnet2 and should rather // assign reserved addresses. AllocEngine::ClientContext4 ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(), false, false, "host.example.com.", true); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); AllocEngine::findReservation(ctx); Lease4Ptr lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("10.1.2.105", lease->addr_.toText()); EXPECT_EQ(lease->subnet_id_, subnet2_->getID()); // Let's create a lease for the client to make sure the lease is not // renewed but a reserved lease is offered. Lease4Ptr lease2(new Lease4(IOAddress("192.0.2.17"), hwaddr_, ClientIdPtr(), 501, time(NULL), subnet1_->getID())); lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease2)); ctx.subnet_ = subnet1_; ctx.hosts_.clear(); AllocEngine::findReservation(ctx); lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("10.1.2.105", lease->addr_.toText()); EXPECT_EQ(lease->subnet_id_, subnet2_->getID()); } // Test that reservations within shared network take precedence over the // existing leases regardless in which subnet belonging to a shared network // reservations belong. TEST_F(SharedNetworkAlloc4Test, discoverSharedNetworkReservations) { EXPECT_FALSE(HostMgr::instance().getDisableSingleQuery()); // Create reservation for the client. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet2_->getID(), SUBNET_ID_UNUSED, IOAddress("10.1.2.105"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); // Start allocation from subnet1. The engine should determine that the // client has reservations in subnet2 and should rather assign reserved // addresses. AllocEngine::ClientContext4 ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(), false, false, "host.example.com.", true); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); AllocEngine::findReservation(ctx); Lease4Ptr lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("10.1.2.105", lease->addr_.toText()); EXPECT_EQ(lease->subnet_id_, subnet2_->getID()); // Let's create a lease for the client in subnet1 to make sure the lease // is not renewed but a reserved lease is offered. Lease4Ptr lease2(new Lease4(IOAddress("192.0.2.17"), hwaddr_, ClientIdPtr(), 501, time(NULL), subnet1_->getID())); lease2->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease2)); ctx.subnet_ = subnet1_; ctx.hosts_.clear(); AllocEngine::findReservation(ctx); lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("10.1.2.105", lease->addr_.toText()); EXPECT_EQ(lease->subnet_id_, subnet2_->getID()); // Let's create a lease for the client in subnet2 to make sure the lease // is not renewed but a reserved lease is offered. Lease4Ptr lease3(new Lease4(IOAddress("10.1.2.55"), hwaddr_, ClientIdPtr(), 501, time(NULL), subnet2_->getID())); lease3->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease3)); ctx.subnet_ = subnet1_; ctx.hosts_.clear(); AllocEngine::findReservation(ctx); lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("10.1.2.105", lease->addr_.toText()); EXPECT_EQ(lease->subnet_id_, subnet2_->getID()); } // Test that reservations within shared network take precedence over the // existing leases regardless in which subnet belonging to a shared network // reservations belong. Host lookups returning a collection are disabled. // As it is only an optimization the behavior (so the test) must stay // unchanged. TEST_F(SharedNetworkAlloc4Test, discoverSharedNetworkReservationsNoColl) { // Disable host lookups returning a collection. ASSERT_FALSE(HostMgr::instance().getDisableSingleQuery()); HostMgr::instance().setDisableSingleQuery(true); // Create reservation for the client. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet2_->getID(), SUBNET_ID_UNUSED, IOAddress("10.1.2.105"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); subnet1_->setReservationsGlobal(true); subnet1_->setReservationsInSubnet(true); subnet2_->setReservationsGlobal(true); subnet2_->setReservationsInSubnet(true); // Start allocation from subnet1. The engine should determine that the // client has reservations in subnet2 and should rather assign reserved // addresses. AllocEngine::ClientContext4 ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(), false, false, "host.example.com.", true); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); AllocEngine::findReservation(ctx); Lease4Ptr lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("10.1.2.105", lease->addr_.toText()); EXPECT_EQ(lease->subnet_id_, subnet2_->getID()); // Let's create a lease for the client to make sure the lease is not // renewed but a reserved lease is offered. Lease4Ptr lease2(new Lease4(IOAddress("192.0.2.17"), hwaddr_, ClientIdPtr(), 501, time(NULL), subnet1_->getID())); lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease2)); ctx.subnet_ = subnet1_; ctx.hosts_.clear(); AllocEngine::findReservation(ctx); lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("10.1.2.105", lease->addr_.toText()); EXPECT_EQ(lease->subnet_id_, subnet2_->getID()); } // 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(SharedNetworkAlloc4Test, runningOut) { // Allocate everything in subnet1 insertLease("192.0.2.17", subnet1_->getID()); // Allocate everything, except one address in subnet2. for (int i = 5; i < 100; i++) { stringstream tmp; tmp << "10.1.2." << 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 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. AllocEngine::ClientContext4 ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(), false, false, "host.example.com.", true); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); Lease4Ptr lease = engine_.allocateLease4(ctx); EXPECT_TRUE(lease); // Now allocate the last address. Now both subnets are exhausted. insertLease("10.1.2.100", subnet2_->getID()); // Ok, we're out. We should not get anything now. lease = engine_.allocateLease4(ctx); ASSERT_FALSE(lease); EXPECT_EQ(1, getStatistics("v4-allocation-fail")); EXPECT_EQ(1, getStatistics("v4-allocation-fail-shared-network")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes")); EXPECT_EQ(1, getStatistics("v4-allocation-fail", subnet1_->getID())); EXPECT_EQ(1, getStatistics("v4-allocation-fail-shared-network", subnet1_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet1_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet1_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet1_->getID())); } // 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(SharedNetworkAlloc4Test, requestSharedNetworkSimple) { // 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. AllocEngine::ClientContext4 ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(), false, false, "host.example.com.", false); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); Lease4Ptr lease = engine_.allocateLease4(ctx); // The allocation engine should have assigned an address from the first // subnet. ASSERT_TRUE(lease); EXPECT_EQ("192.0.2.17", lease->addr_.toText()); // Make sure the lease is in the lease mgr. ASSERT_TRUE(LeaseMgrFactory::instance().getLease4(lease->addr_)); } // This test verifies that the server can allocate an address from a // different subnet than orginally selected, when the address pool in // the first subnet is exhausted. TEST_F(SharedNetworkAlloc4Test, requestSharedNetwork) { // Create a lease for a single address in the first address pool. The // pool is now exhausted. Lease4Ptr lease = insertLease("192.0.2.17", subnet1_->getID()); // 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. AllocEngine::ClientContext4 ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(), false, false, "host.example.com.", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); Lease4Ptr lease2 = engine_.allocateLease4(ctx); // The allocation engine should have assigned an address from the second // subnet. We could guess that this is 10.1.2.5, being the first address // in the address pool, but to make the test more generic, we merely // verify that the address is in the given address pool. ASSERT_TRUE(lease2); EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease2->addr_)); ASSERT_TRUE(LeaseMgrFactory::instance().deleteLease(lease2)); // The client should also be assigned a lease when it specifies a hint // that doesn't match the subnet from which the lease is offered. The // engine should check alternative subnets to match the hint to // a subnet. The requested lease is available, so it should be offered. ctx.subnet_ = subnet1_; ctx.requested_address_ = IOAddress("10.1.2.25"); lease2 = engine_.allocateLease4(ctx); ASSERT_TRUE(lease2); EXPECT_EQ("10.1.2.25", lease2->addr_.toText()); // The returning client (the one that has a lease) should also be able // to renew its lease regardless of a subnet it begins with. So, it has // an address assigned from subnet1, but we use subnet2 as a selected // subnet. AllocEngine::ClientContext4 ctx2(subnet2_, ClientIdPtr(), hwaddr2_, IOAddress("0.0.0.0"), false, false, "host.example.com.", false); ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1234)); lease2 = engine_.allocateLease4(ctx2); // The existing lease should be returned. ASSERT_TRUE(lease2); EXPECT_EQ("192.0.2.17", lease2->addr_.toText()); } // This test verifies that the server can assign an address from a // different subnet than orginally selected, when the address pool in // the first subnet is exhausted. TEST_F(SharedNetworkAlloc4Test, requestSharedNetworkClassification) { // Try to offer address from subnet1. There is one address available // so it should be offered. AllocEngine::ClientContext4 ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(), false, false, "host.example.com.", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); Lease4Ptr lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_TRUE(subnet1_->inPool(Lease::TYPE_V4, lease->addr_)); // Remove the lease so as we can start over. ASSERT_TRUE(LeaseMgrFactory::instance().deleteLease(lease)); // 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 assign an address from subnet2 that belongs // to the same shared network. ctx.subnet_ = subnet1_; lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_)); // Remove the lease so as we can start over. ASSERT_TRUE(LeaseMgrFactory::instance().deleteLease(lease)); // Assign cable-modem class and try again. This time, we should // offer an address from the subnet1. ctx.query_->addClass(ClientClass("cable-modem")); ctx.subnet_ = subnet1_; lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_TRUE(subnet1_->inPool(Lease::TYPE_V4, lease->addr_)); // Let's now remove the client from the cable-modem class and try // to renew the address. The engine should determine that the // client doesn't have access to the subnet1 pools anymore and // assign an address from unrestricted subnet. ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); ctx.subnet_ = subnet1_; lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_)); } // This test verifies that the server can assign an address from a // different subnet than orginally selected, when the address pool in // the first subnet requires another class. TEST_F(SharedNetworkAlloc4Test, requestSharedNetworkPoolClassification) { // Try to offer address from subnet1. There is one address available // so it should be offered. AllocEngine::ClientContext4 ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(), false, false, "host.example.com.", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); Lease4Ptr lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_TRUE(subnet1_->inPool(Lease::TYPE_V4, lease->addr_)); // Remove the lease so as we can start over. ASSERT_TRUE(LeaseMgrFactory::instance().deleteLease(lease)); // 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 assign an address from subnet2 that belongs // to the same shared network. ctx.subnet_ = subnet1_; lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_)); // Remove the lease so as we can start over. ASSERT_TRUE(LeaseMgrFactory::instance().deleteLease(lease)); // Assign cable-modem class and try again. This time, we should // offer an address from the pool1. ctx.query_->addClass(ClientClass("cable-modem")); // Restrict access to pool2 for this client, to make sure that the // server doesn't accidentally get an address from this pool. pool2_->allowClientClass("telephone"); ctx.subnet_ = subnet1_; lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_TRUE(subnet1_->inPool(Lease::TYPE_V4, lease->addr_)); // Let's now remove the client from the cable-modem class and try // to renew the address. The engine should determine that the // client doesn't have access to the pool1 anymore and // assign an address from another pool. ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); ctx.query_->addClass(ClientClass("telephone")); ctx.subnet_ = subnet1_; lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_TRUE(subnet2_->inPool(Lease::TYPE_V4, lease->addr_)); } // Test that reservations within shared network take precedence over the // existing leases regardless in which subnet belonging to a shared network // reservations belong (DHCPREQUEST case). TEST_F(SharedNetworkAlloc4Test, requestSharedNetworkReservations) { EXPECT_FALSE(HostMgr::instance().getDisableSingleQuery()); // Create reservation for the client. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet2_->getID(), SUBNET_ID_UNUSED, IOAddress("10.1.2.105"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); // Start allocation from subnet1. The engine should determine that the // client has reservations in subnet2 and should rather assign reserved // addresses. AllocEngine::ClientContext4 ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(), false, false, "host.example.com.", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx); Lease4Ptr lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("10.1.2.105", lease->addr_.toText()); EXPECT_EQ(lease->subnet_id_, subnet2_->getID()); // Remove the lease for another test below. ASSERT_TRUE(LeaseMgrFactory::instance().deleteLease(lease)); // Let's create a lease for the client to make sure the lease is not // renewed but a reserved lease is allocated again. Lease4Ptr lease2(new Lease4(IOAddress("192.0.2.17"), hwaddr_, ClientIdPtr(), 501, time(NULL), subnet1_->getID())); lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease2)); ctx.subnet_ = subnet1_; ctx.hosts_.clear(); AllocEngine::findReservation(ctx); lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("10.1.2.105", lease->addr_.toText()); EXPECT_EQ(lease->subnet_id_, subnet2_->getID()); } // Test that reservations within shared network take precedence over the // existing leases regardless in which subnet belonging to a shared network // reservations belong (DHCPREQUEST case). Host lookups returning a collection // are disabled. As it is only an optimization the behavior (so the test) // must stay unchanged. TEST_F(SharedNetworkAlloc4Test, requestSharedNetworkReservationsNoColl) { // Disable host lookups returning a collection. ASSERT_FALSE(HostMgr::instance().getDisableSingleQuery()); HostMgr::instance().setDisableSingleQuery(true); // Create reservation for the client. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet2_->getID(), SUBNET_ID_UNUSED, IOAddress("10.1.2.105"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); // Start allocation from subnet1. The engine should determine that the // client has reservations in subnet2 and should rather assign reserved // addresses. AllocEngine::ClientContext4 ctx(subnet1_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(), false, false, "host.example.com.", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx); Lease4Ptr lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("10.1.2.105", lease->addr_.toText()); EXPECT_EQ(lease->subnet_id_, subnet2_->getID()); // Remove the lease for another test below. ASSERT_TRUE(LeaseMgrFactory::instance().deleteLease(lease)); // Let's create a lease for the client to make sure the lease is not // renewed but a reserved lease is allocated again. Lease4Ptr lease2(new Lease4(IOAddress("192.0.2.17"), hwaddr_, ClientIdPtr(), 501, time(NULL), subnet1_->getID())); lease->cltt_ = time(NULL) - 10; // Allocated 10 seconds ago ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease2)); ctx.subnet_ = subnet1_; ctx.hosts_.clear(); AllocEngine::findReservation(ctx); lease = engine_.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("10.1.2.105", lease->addr_.toText()); EXPECT_EQ(lease->subnet_id_, subnet2_->getID()); } // This test checks if an expired lease can be reused in DHCPDISCOVER (fake // allocation) TEST_F(AllocEngine4Test, discoverReuseExpiredLease4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); IOAddress addr("192.0.2.15"); CfgMgr& cfg_mgr = CfgMgr::instance(); // Get rid of the default test configuration. cfg_mgr.clear(); // Create configuration similar to other tests, but with a single address pool subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10)); pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address subnet_->addPool(pool_); cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_); // Just a different hw/client-id for the second client uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe }; HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER)); uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 }; time_t now = time(NULL) - 500; // Allocated 500 seconds ago Lease4Ptr lease(new Lease4(addr, hwaddr2, clientid2, sizeof(clientid2), 495, now, subnet_->getID())); // Copy the lease, so as it can be compared with the old lease returned // by the allocation engine. Lease4 original_lease(*lease); // Lease was assigned 500 seconds ago, but its valid lifetime is 495, so it // is expired already ASSERT_TRUE(lease->expired()); ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); // CASE 1: Asking for any address AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "", true); ctx1.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); lease = engine->allocateLease4(ctx1); // Check that we got that single lease ASSERT_TRUE(lease); EXPECT_EQ(addr, lease->addr_); // We are reusing expired lease, the old (expired) instance should be // returned. The returned instance should be the same as the original // lease. ASSERT_TRUE(ctx1.old_lease_); EXPECT_TRUE(original_lease == *ctx1.old_lease_); // Do all checks on the lease (if subnet-id, preferred/valid times are ok etc.) checkLease4(lease); // CASE 2: Asking specifically for this address AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_, IOAddress(addr), false, false, "", true); ctx2.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); lease = engine->allocateLease4(ctx2); // Check that we got that single lease ASSERT_TRUE(lease); EXPECT_EQ(addr, lease->addr_); // We are updating expired lease. The copy of the old lease should be // returned and it should be equal to the original lease. ASSERT_TRUE(ctx2.old_lease_); EXPECT_TRUE(*ctx2.old_lease_ == original_lease); } // This test checks if an expired lease can be reused in REQUEST (actual allocation) TEST_F(AllocEngine4Test, requestReuseExpiredLease4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); IOAddress addr("192.0.2.105"); EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID())); int64_t cumulative = getStatistics("cumulative-assigned-addresses", subnet_->getID()); int64_t glbl_cumulative = getStatistics("cumulative-assigned-addresses"); EXPECT_TRUE(testStatistics("reclaimed-leases", 0)); EXPECT_TRUE(testStatistics("reclaimed-leases", 0, subnet_->getID())); // Just a different hw/client-id for the second client uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe }; HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER)); uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 }; time_t now = time(NULL) - 500; // Allocated 500 seconds ago Lease4Ptr lease(new Lease4(addr, hwaddr2, clientid2, sizeof(clientid2), 495, now, subnet_->getID())); // Make a copy of the lease, so as we can compare that with the old lease // instance returned by the allocation engine. Lease4 original_lease(*lease); // Lease was assigned 500 seconds ago, but its valid lifetime is 495, so it // is expired already ASSERT_TRUE(lease->expired()); ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); // A client comes along, asking specifically for this address AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress(addr), false, false, "host.example.com.", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); lease = engine->allocateLease4(ctx); // Check that he got that single lease ASSERT_TRUE(lease); EXPECT_EQ(addr, lease->addr_); // Check that the lease is indeed updated in LeaseMgr Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr); ASSERT_TRUE(from_mgr); // Now check that the lease in LeaseMgr has the same parameters detailCompareLease(lease, from_mgr); // The allocation engine should return a copy of the old lease. This // lease should be equal to the original lease. ASSERT_TRUE(ctx.old_lease_); EXPECT_TRUE(*ctx.old_lease_ == original_lease); // Check that the stats declined stats were modified correctly. Note, because // added the lease directly, assigned-leases never bumped to one, so when we // reclaim it gets decremented to -1, then on assignment back to 0. EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID())); cumulative += 1; EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", cumulative, subnet_->getID())); glbl_cumulative += 1; EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", glbl_cumulative)); EXPECT_TRUE(testStatistics("reclaimed-leases", 1)); EXPECT_TRUE(testStatistics("reclaimed-leases", 1, subnet_->getID())); } // This test checks if an expired declined lease can be reused when responding // to DHCPDISCOVER (fake allocation) TEST_F(AllocEngine4Test, discoverReuseDeclinedLease4) { AllocEnginePtr engine(new AllocEngine(0)); ASSERT_TRUE(engine); // Now prepare a configuration with single address pool. IOAddress addr("192.0.2.15"); CfgMgr& cfg_mgr = CfgMgr::instance(); cfg_mgr.clear(); subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10)); pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address subnet_->addPool(pool_); cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_); // Now create a declined lease, decline it and rewind its cltt, so it // is expired. Lease4Ptr declined = generateDeclinedLease("192.0.2.15", 100, -10); // CASE 1: Ask for any address Lease4Ptr assigned; testReuseLease4(engine, declined, "0.0.0.0", true, SHOULD_PASS, assigned); // Check that we got that single lease ASSERT_TRUE(assigned); EXPECT_EQ(addr, assigned->addr_); // CASE 2: Asking specifically for this address testReuseLease4(engine, declined, "192.0.2.15", true, SHOULD_PASS, assigned); // Check that we get it again ASSERT_TRUE(assigned); EXPECT_EQ(addr, assigned->addr_); } // This test checks if statistics are not updated when expired declined lease // is reused when responding to DHCPDISCOVER (fake allocation) TEST_F(AllocEngine4Test, discoverReuseDeclinedLease4Stats) { // Now prepare for DISCOVER processing AllocEnginePtr engine(new AllocEngine(0)); ASSERT_TRUE(engine); // Now prepare a configuration with single address pool. IOAddress addr("192.0.2.15"); CfgMgr& cfg_mgr = CfgMgr::instance(); cfg_mgr.clear(); subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10)); pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address subnet_->addPool(pool_); cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_); cfg_mgr.commit(); // so we will recalc stats int64_t cumulative = getStatistics("cumulative-assigned-addresses", subnet_->getID()); int64_t glbl_cumulative = getStatistics("cumulative-assigned-addresses"); // Now create a declined lease, decline it and rewind its cltt, so it // is expired. Lease4Ptr declined = generateDeclinedLease("192.0.2.15", 100, -10); // Ask for any address. There's only one address in the pool, so it doesn't // matter much. Lease4Ptr assigned; testReuseLease4(engine, declined, "0.0.0.0", true, SHOULD_PASS, assigned); // Check that the stats declined stats were not modified EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID())); EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", cumulative, subnet_->getID())); EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", glbl_cumulative)); EXPECT_TRUE(testStatistics("declined-addresses", 0)); EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0)); EXPECT_TRUE(testStatistics("declined-addresses", 0, subnet_->getID())); EXPECT_TRUE(testStatistics("reclaimed-declined-addresses", 0, subnet_->getID())); } // This test checks if an expired declined lease can be reused when responding // to REQUEST (actual allocation) TEST_F(AllocEngine4Test, requestReuseDeclinedLease4) { AllocEnginePtr engine(new AllocEngine(0)); ASSERT_TRUE(engine); // Now prepare a configuration with single address pool. IOAddress addr("192.0.2.15"); CfgMgr& cfg_mgr = CfgMgr::instance(); cfg_mgr.clear(); subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10)); pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address subnet_->addPool(pool_); cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_); // Now create a declined lease, decline it and rewind its cltt, so it // is expired. Lease4Ptr declined = generateDeclinedLease("192.0.2.15", 100, -10); // Asking specifically for this address Lease4Ptr assigned; testReuseLease4(engine, declined, "192.0.2.15", 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 Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(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 DHCPREQUEST (actual allocation) TEST_F(AllocEngine4Test, requestReuseDeclinedLease4Stats) { AllocEnginePtr engine(new AllocEngine(0)); ASSERT_TRUE(engine); // Now prepare a configuration with single address pool. IOAddress addr("192.0.2.15"); CfgMgr& cfg_mgr = CfgMgr::instance(); cfg_mgr.clear(); subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10)); pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address subnet_->addPool(pool_); cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_); cfg_mgr.commit(); int64_t cumulative = getStatistics("cumulative-assigned-addresses", subnet_->getID()); int64_t glbl_cumulative = getStatistics("cumulative-assigned-addresses"); // Now create a declined lease, decline it and rewind its cltt, so it // is expired. Lease4Ptr declined = generateDeclinedLease("192.0.2.15", 100, -10); // Asking specifically for this address Lease4Ptr assigned; testReuseLease4(engine, declined, "192.0.2.15", false, SHOULD_PASS, assigned); // Check that we got it. ASSERT_TRUE(assigned); // Check that the stats are correct. Note that assigned-addresses does // not get decremented when a lease is declined, ergo not incremented // when it is reused. Declined address stats will be -1 since // lease was created as declined which does not increment the stat. EXPECT_TRUE(testStatistics("assigned-addresses", 0, subnet_->getID())); cumulative += 1; EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", cumulative, subnet_->getID())); glbl_cumulative += 1; EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", 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 that the Allocation Engine correctly identifies the // existing client's lease in the lease database, using the client // identifier and HW address. TEST_F(AllocEngine4Test, identifyClientLease) { Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_, clientid_, 100, time(NULL), subnet_->getID())); LeaseMgrFactory::instance().addLease(lease); AllocEngine engine(0); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(), false, false, "", true); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); Lease4Ptr identified_lease = engine.allocateLease4(ctx); ASSERT_TRUE(identified_lease); EXPECT_EQ("192.0.2.101", identified_lease->addr_.toText()); ctx.hwaddr_ = hwaddr2_; ctx.clientid_ = clientid_; identified_lease = engine.allocateLease4(ctx); ASSERT_TRUE(identified_lease); EXPECT_EQ("192.0.2.101", identified_lease->addr_.toText()); ctx.hwaddr_ = hwaddr_; ctx.clientid_ = clientid2_; identified_lease = engine.allocateLease4(ctx); ASSERT_TRUE(identified_lease); EXPECT_NE(identified_lease->addr_.toText(), "192.0.2.101"); ctx.hwaddr_ = hwaddr_; ctx.clientid_.reset(); identified_lease = engine.allocateLease4(ctx); ASSERT_TRUE(identified_lease); EXPECT_EQ("192.0.2.101", identified_lease->addr_.toText()); ctx.hwaddr_ = hwaddr2_; ctx.clientid_.reset(); identified_lease = engine.allocateLease4(ctx); ASSERT_TRUE(identified_lease); EXPECT_NE(identified_lease->addr_.toText(), "192.0.2.101"); lease->client_id_.reset(); ASSERT_NO_THROW(LeaseMgrFactory::instance().updateLease4(lease)); ctx.hwaddr_ = hwaddr_; ctx.clientid_ = clientid_; identified_lease = engine.allocateLease4(ctx); ASSERT_TRUE(identified_lease); EXPECT_EQ("192.0.2.101", identified_lease->addr_.toText()); ctx.hwaddr_ = hwaddr_; ctx.clientid_.reset(); identified_lease = engine.allocateLease4(ctx); ASSERT_TRUE(identified_lease); EXPECT_EQ("192.0.2.101", identified_lease->addr_.toText()); ctx.hwaddr_ = hwaddr2_; ctx.clientid_ = clientid_; identified_lease = engine.allocateLease4(ctx); ASSERT_TRUE(identified_lease); EXPECT_NE(identified_lease->addr_.toText(), "192.0.2.101"); } // This test checks that when the client requests the address which belongs // to another client, the allocation engine returns NULL (for the // DHCPREQUEST case) or a lease for the address which belongs to this // client (DHCPDISCOVER case). TEST_F(AllocEngine4Test, requestOtherClientLease) { // Create the first lease. Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_, &clientid_->getClientId()[0], clientid_->getClientId().size(), 100, time(NULL), subnet_->getID(), false, false, "")); // Create the second lease. Note that we use the same client id here and // we expect that the allocation engine will figure out that the hardware // address is different. Lease4Ptr lease2(new Lease4(IOAddress("192.0.2.102"), hwaddr2_, &clientid_->getClientId()[0], clientid_->getClientId().size(), 100, time(NULL), subnet_->getID(), false, false, "")); // Add leases for both clients to the Lease Manager. LeaseMgrFactory::instance().addLease(lease); LeaseMgrFactory::instance().addLease(lease2); AllocEngine engine(0); // First client requests the lease which belongs to the second client. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("192.0.2.102"), false, false, "", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); Lease4Ptr new_lease = engine.allocateLease4(ctx); // Allocation engine should return NULL. ASSERT_FALSE(new_lease); EXPECT_EQ(0, getStatistics("v4-allocation-fail")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes")); EXPECT_EQ(0, getStatistics("v4-allocation-fail", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID())); // Now simulate the DHCPDISCOVER case when the provided address is // treated as a hint. The engine should return a lease for a // different address than requested. ctx.fake_allocation_ = true; new_lease = engine.allocateLease4(ctx); ASSERT_TRUE(new_lease); EXPECT_EQ("192.0.2.101", new_lease->addr_.toText()); } // This test checks the behavior of the allocation engine in the following // scenario: // - Client has no lease in the database. // - Client has a reservation. // - Client sends DHCPREQUEST without requested IP Address, nor ciaddr. // - Client is allocated a reserved address. // // Note that client must normally include a requested IP address or ciaddr // in its message. But, we still want to provision clients that don't do that. // The server simply picks reserved address or any other available one if there // is no reservation. TEST_F(AllocEngine4Test, reservedAddressNoHint) { // Create reservation for the client. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("192.0.2.123"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); AllocEngine engine(0); // Try to allocate a lease without specifying a hint. This is actually // incorrect behavior of the client to not send an address it wants to // obtain but the server should handle this gracefully. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx); Lease4Ptr lease = engine.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("192.0.2.123", lease->addr_.toText()); // Make sure that the lease has been committed to the lease database. // And that the committed lease is equal to the one returned. Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); detailCompareLease(lease, from_mgr); // Initially, there was no lease for this client, so the returned old // lease should be NULL. ASSERT_FALSE(ctx.old_lease_); } // This test checks behavior of the allocation engine in the following scenario: // - Client has no lease in the database. // - Client has a reservation. // - Client sends DHCPDISCOVER without requested IP Address. // - Server returns DHCPOFFER with the reserved address. TEST_F(AllocEngine4Test, reservedAddressNoHintFakeAllocation) { // Create reservation for the client. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("192.0.2.123"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); AllocEngine engine(0); // Query allocation engine for the lease to be assigned to this // client without specifying the address to be assigned. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "", true); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); AllocEngine::findReservation(ctx); Lease4Ptr lease = engine.allocateLease4(ctx); ASSERT_TRUE(lease); // The allocation engine should return a reserved address. EXPECT_EQ("192.0.2.123", lease->addr_.toText()); // This is a "fake" allocation so the returned lease should not be committed // to the lease database. EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_)); // Client had no lease in the database, so the old lease returned should // be NULL. ASSERT_FALSE(ctx.old_lease_); } // This test checks the behavior of the allocation engine in the following // scenario: // - Client has no lease in the database. // - Client has a reservation. // - Client sends DHCPREQUEST with a requested IP address // - Server returns DHCPNAK when requested IP address is different than // the reserved address. Note that the allocation engine returns NULL // to indicate to the server that it should send DHCPNAK. // - Server allocates a reserved address to the client when the client requests // this address using requested IP address option. TEST_F(AllocEngine4Test, reservedAddressHint) { HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("192.0.2.123"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); AllocEngine engine(0); AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_, IOAddress("192.0.2.234"), false, false, "", false); ctx1.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx1); Lease4Ptr lease = engine.allocateLease4(ctx1); // The client requested a different address than reserved, so // the allocation engine should return NULL lease. When the server // receives a NULL lease for the client, it will send a DHCPNAK. ASSERT_FALSE(lease); ASSERT_FALSE(ctx1.old_lease_); EXPECT_EQ(0, getStatistics("v4-allocation-fail")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes")); EXPECT_EQ(0, getStatistics("v4-allocation-fail", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID())); // Now, request a correct address. The client should obtain it. AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_, IOAddress("192.0.2.123"), false, false, "", false); ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx2); lease = engine.allocateLease4(ctx2); ASSERT_TRUE(lease); EXPECT_EQ("192.0.2.123", lease->addr_.toText()); // Make sure that the lease has been committed to the lease database. // And that the committed lease is equal to the one returned. Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); detailCompareLease(lease, from_mgr); ASSERT_FALSE(ctx2.old_lease_); } // This test checks the behavior of the allocation engine in the following // scenario: // - Client has no lease in the database. // - Client has a reservation. // - Client sends DHCPDISCOVER with a requested IP address as a hint. // - Server offers a reserved address, even though it is different than the // requested address. TEST_F(AllocEngine4Test, reservedAddressHintFakeAllocation) { // Create a reservation for the client. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("192.0.2.123"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); AllocEngine engine(0); // Query the allocation engine for the lease to be assigned to the client // and specify a hint being a different address than the reserved one. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("192.0.2.234"), false, false, "", true); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx); Lease4Ptr lease = engine.allocateLease4(ctx); ASSERT_TRUE(lease); // Allocation engine should return reserved address. EXPECT_EQ("192.0.2.123", lease->addr_.toText()); // This is a "fake" allocation so the returned lease should not be committed // to the lease database. EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_)); ASSERT_FALSE(ctx.old_lease_); } // This test checks that the behavior of the allocation engine in the following // scenario: // - Client has a lease for the address from the dynamic pool in the database. // - Client has a reservation for a different address than the one for which // the client has a lease. // - Client sends DHCPREQUEST, asking for the reserved address (as it has been // offered to it when it sent DHCPDISCOVER). // - Server allocates a reserved address and removes the lease for the address // previously allocated to the client. TEST_F(AllocEngine4Test, reservedAddressExistingLease) { // Create the reservation for the client. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("192.0.2.123"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); // Create a lease for the client with a different address than the reserved // one. Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_, &clientid_->getClientId()[0], clientid_->getClientId().size(), 100, time(NULL), subnet_->getID(), false, false, "")); LeaseMgrFactory::instance().addLease(lease); AllocEngine engine(0); // Request allocation of the reserved address. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("192.0.2.123"), false, false, "", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx); Lease4Ptr allocated_lease = engine.allocateLease4(ctx); ASSERT_TRUE(allocated_lease); // The engine should have allocated the reserved address. EXPECT_EQ("192.0.2.123", allocated_lease->addr_.toText()); // Make sure that the lease has been committed to the lease database. Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(allocated_lease->addr_); ASSERT_TRUE(from_mgr); detailCompareLease(allocated_lease, from_mgr); // The previous lease should have been replaced by a new one. The previous // lease should be returned by the allocation engine to the caller. ASSERT_TRUE(ctx.old_lease_); EXPECT_EQ("192.0.2.101", ctx.old_lease_->addr_.toText()); detailCompareLease(ctx.old_lease_, lease); } // This test checks that the behavior of the allocation engine in the following // scenario: // - Client A has a lease in the database. // - Client B has a reservation for the address in use by client A. // - Client B sends a DHCPREQUEST requesting the allocation of the reserved // lease (in use by client A). // - Server determines that the reserved address is in use by a different client // and returns DHCPNAK to client B. TEST_F(AllocEngine4Test, reservedAddressHijacked) { // Create host reservation for the client B. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("192.0.2.123"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); // Allocate a lease for the client A for the same address as reserved // for the client B. Lease4Ptr lease(new Lease4(IOAddress("192.0.2.123"), hwaddr2_, 0, 0, 100, time(NULL), subnet_->getID(), false, false, "")); LeaseMgrFactory::instance().addLease(lease); AllocEngine engine(0); // Try to allocate the reserved lease to client B. AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_, IOAddress("192.0.2.123"), false, false, "", false); ctx1.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx1); Lease4Ptr allocated_lease = engine.allocateLease4(ctx1); // The lease is allocated to someone else, so the allocation should not // succeed. ASSERT_FALSE(allocated_lease); ASSERT_FALSE(ctx1.old_lease_); EXPECT_EQ(0, getStatistics("v4-allocation-fail")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes")); EXPECT_EQ(0, getStatistics("v4-allocation-fail", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID())); // Make sure that the allocation engine didn't modify the lease of the // client A. Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); detailCompareLease(lease, from_mgr); // Try doing the same thing, but this time do not request any specific // address. It should have the same effect. AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "", false); ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx2); allocated_lease = engine.allocateLease4(ctx2); ASSERT_FALSE(allocated_lease); ASSERT_FALSE(ctx2.old_lease_); EXPECT_EQ(0, getStatistics("v4-allocation-fail")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes")); EXPECT_EQ(0, getStatistics("v4-allocation-fail", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID())); from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); detailCompareLease(lease, from_mgr); } // This test checks that the behavior of the allocation engine in the following // scenario: // - Client A has a lease in the database. // - Client B has a reservation for the address in use by client A. // - Client B sends a DHCPDISCOVER. // - Server determines that the reserved address is in use by a different client // so it offers an address from the dynamic pool. TEST_F(AllocEngine4Test, reservedAddressHijackedFakeAllocation) { // Create a reservation for the client B. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("192.0.2.123"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); // Create a lease for the client A. Lease4Ptr lease(new Lease4(IOAddress("192.0.2.123"), hwaddr2_, 0, 0, 100, time(NULL), subnet_->getID(), false, false, "")); LeaseMgrFactory::instance().addLease(lease); AllocEngine engine(0); // Query allocation engine for the lease to be allocated to the client B. // The allocation engine is not able to allocate the lease to the client // B, because the address is in use by client A. AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_, IOAddress("192.0.2.123"), false, false, "", true); ctx1.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx1); Lease4Ptr allocated_lease = engine.allocateLease4(ctx1); // The allocation engine should return a lease but for a different address // than requested because this address is in use. ASSERT_TRUE(allocated_lease); ASSERT_FALSE(ctx1.old_lease_); EXPECT_NE(allocated_lease->addr_.toText(), "192.0.2.123"); EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, allocated_lease->addr_)); // Do the same test. But, this time do not specify any address to be // allocated. AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "", true); ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx2); allocated_lease = engine.allocateLease4(ctx2); ASSERT_TRUE(allocated_lease); EXPECT_NE(allocated_lease->addr_.toText(), "192.0.2.123"); EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, allocated_lease->addr_)); ASSERT_FALSE(ctx2.old_lease_); } // This test checks that the behavior of the allocation engine in the following // scenario: // - Client has a reservation. // - Client has a lease in the database for a different address than reserved. // - Client sends a DHCPREQUEST and asks for a different address than reserved, // and different than it has in a database. // - Server doesn't allocate the reserved address to the client because the // client asked for the different address. // // Note that in this case the client should get the DHCPNAK and should fall back // to the DHCPDISCOVER. TEST_F(AllocEngine4Test, reservedAddressExistingLeaseInvalidHint) { // Create a reservation for the client. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("192.0.2.123"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); // Create a lease for the client for a different address than reserved. Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_, ClientIdPtr(), 100, time(NULL), subnet_->getID(), false, false, "")); LeaseMgrFactory::instance().addLease(lease); AllocEngine engine(0); // Try to allocate a lease and specify a different address than reserved // and different from the one that client is currently using. AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_, IOAddress("192.0.2.102"), false, false, "", false); ctx1.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx1); Lease4Ptr allocated_lease = engine.allocateLease4(ctx1); ASSERT_FALSE(allocated_lease); ASSERT_FALSE(ctx1.old_lease_); EXPECT_EQ(0, getStatistics("v4-allocation-fail")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes")); EXPECT_EQ(0, getStatistics("v4-allocation-fail", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID())); // Repeat the test, but this time ask for the address that the client // has allocated. AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_, IOAddress("192.0.2.101"), false, false, "", false); ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx2); allocated_lease = engine.allocateLease4(ctx2); // The client has reservation so the server wants to allocate a // reserved address and doesn't want to renew the address that the // client is currently using. This is equivalent of the case when // the client tries to renew the lease but there is a new reservation // for this client. The server doesn't allow for the renewal and // responds with DHCPNAK to force the client to return to the // DHCP server discovery. ASSERT_FALSE(allocated_lease); ASSERT_FALSE(ctx2.old_lease_); EXPECT_EQ(0, getStatistics("v4-allocation-fail")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes")); EXPECT_EQ(0, getStatistics("v4-allocation-fail", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID())); } // This test checks that the behavior of the allocation engine in the following // scenario: // - Client has a lease in the database. // - Client has a reservation for a different address than the one for which it // has a lease. // - Client sends a DHCPDISCOVER and asks for a different address than reserved // and different from which it has a lease for. // - Server ignores the client's hint and offers a reserved address. TEST_F(AllocEngine4Test, reservedAddressExistingLeaseFakeAllocation) { // Create a reservation for the client. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("192.0.2.123"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); // Create a lease for a different address than reserved. Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_, &clientid_->getClientId()[0], clientid_->getClientId().size(), 100, time(NULL), subnet_->getID(), false, false, "")); LeaseMgrFactory::instance().addLease(lease); AllocEngine engine(0); // Try to allocate a lease and use a completely different address // as a hint. AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_, IOAddress("192.0.2.102"), false, false, "", true); ctx1.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); AllocEngine::findReservation(ctx1); Lease4Ptr allocated_lease = engine.allocateLease4(ctx1); // Server should offer a lease for a reserved address. ASSERT_TRUE(allocated_lease); EXPECT_EQ("192.0.2.123", allocated_lease->addr_.toText()); // The lease should not be allocated until the client sends a DHCPREQUEST. EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(allocated_lease->addr_)); // Old lease should contain the currently used lease. ASSERT_TRUE(ctx1.old_lease_); EXPECT_EQ("192.0.2.101", ctx1.old_lease_->addr_.toText()); // Repeat the test but this time ask for the address for which the // client has a lease. AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_, IOAddress("192.0.2.101"), false, false, "", true); ctx2.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); AllocEngine::findReservation(ctx2); allocated_lease = engine.allocateLease4(ctx2); // The server should offer the lease, but not for the address that // the client requested. The server should offer a reserved address. ASSERT_TRUE(allocated_lease); EXPECT_EQ("192.0.2.123", allocated_lease->addr_.toText()); // Old lease should contain the currently used lease. ASSERT_TRUE(ctx2.old_lease_); EXPECT_EQ("192.0.2.101", ctx2.old_lease_->addr_.toText()); } // This test checks that the behavior of the allocation engine in the following // scenario: // - Client has a reservation. // - Client has a lease for a different address than reserved. // - Client sends a DHCPREQUEST to allocate a lease. // - The server determines that the client has a reservation for the // different address than it is currently using and should assign // a reserved address and remove the previous lease. TEST_F(AllocEngine4Test, reservedAddressExistingLeaseNoHint) { // Create a reservation. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("192.0.2.123"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); // Create a lease for a different address than reserved. Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_, &clientid_->getClientId()[0], clientid_->getClientId().size(), 100, time(NULL), subnet_->getID(), false, false, "")); LeaseMgrFactory::instance().addLease(lease); AllocEngine engine(0); // Try to allocate a lease with providing no hint. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx); Lease4Ptr allocated_lease = engine.allocateLease4(ctx); // The reserved address should be allocated. ASSERT_TRUE(allocated_lease); EXPECT_EQ("192.0.2.123", allocated_lease->addr_.toText()); // The previous lease should be removed. EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_)); // Make sure that the allocated lease is committed in the lease database. Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(allocated_lease->addr_); ASSERT_TRUE(from_mgr); detailCompareLease(allocated_lease, from_mgr); // Old lease should be returned. ASSERT_TRUE(ctx.old_lease_); detailCompareLease(lease, ctx.old_lease_); } // This test checks that the behavior of the allocation engine in the following // scenario: // - Client has a reservation. // - Client has a lease for a different address than reserved. // - Client sends a DHCPDISCOVER with no hint. // - Server determines that there is a reservation for the client and that // the reserved address should be offered when the client sends a // DHCPDISCOVER. TEST_F(AllocEngine4Test, reservedAddressExistingLeaseNoHintFakeAllocation) { // Create a reservation. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("192.0.2.123"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); // Create a lease for a different address than reserved. Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_, &clientid_->getClientId()[0], clientid_->getClientId().size(), 100, time(NULL), subnet_->getID(), false, false, "")); LeaseMgrFactory::instance().addLease(lease); AllocEngine engine(0); // Query the allocation engine for the lease to be allocated for the // client. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "", true); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); AllocEngine::findReservation(ctx); Lease4Ptr allocated_lease = engine.allocateLease4(ctx); // The server should offer the reserved address. ASSERT_TRUE(allocated_lease); EXPECT_EQ("192.0.2.123", allocated_lease->addr_.toText()); // The lease should not be committed to the lease database until the // client sends a DHCPREQUEST. EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(allocated_lease->addr_)); // The old lease should reflect what is in the database. ASSERT_TRUE(ctx.old_lease_); Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); detailCompareLease(lease, from_mgr); } // This test checks that the behavior of the allocation engine in the following // scenario: // - Client A has a lease for the address. // - Client B has a reservation for the same address that the Client A is using. // - Client B requests allocation of the reserved address. // - Server returns DHCPNAK to the client to indicate that the requested address // can't be allocated. // - Client A renews the lease. // - Server determines that the lease that the Client A is trying to renew // is for the address reserved for Client B. Therefore, the server returns // DHCPNAK to force the client to return to the server discovery. // - The Client A sends DHCPDISCOVER. // - The server offers an address to the Client A, which is different than // the address reserved for Client B. // - The Client A requests allocation of the offered address. // - The server allocates the new address to Client A. // - The Client B sends DHCPDISCOVER to the server. // - The server offers a reserved address to the Client B. // - The Client B requests the offered address. // - The server allocates the reserved address to the Client B. TEST_F(AllocEngine4Test, reservedAddressConflictResolution) { // Create a reservation for client B. HostPtr host(new Host(&hwaddr2_->hwaddr_[0], hwaddr2_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("192.0.2.101"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); // Create a lease for Client A. Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_, &clientid_->getClientId()[0], clientid_->getClientId().size(), 100, time(NULL), subnet_->getID(), false, false, "")); LeaseMgrFactory::instance().addLease(lease); AllocEngine engine(0); // Client B sends a DHCPREQUEST to allocate a reserved lease. The // allocation engine can't allocate a reserved lease for this client // because this specific address is in use by the Client A. AllocEngine::ClientContext4 ctx1(subnet_, ClientIdPtr(), hwaddr2_, IOAddress("192.0.2.101"), false, false, "", false); ctx1.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx1); Lease4Ptr offered_lease = engine.allocateLease4(ctx1); ASSERT_FALSE(offered_lease); EXPECT_EQ(0, getStatistics("v4-allocation-fail")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes")); EXPECT_EQ(0, getStatistics("v4-allocation-fail", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID())); // Client A tries to renew the lease. The renewal should fail because // server detects that Client A doesn't have reservation for this // address. AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_, IOAddress("192.0.2.101"), false, false, "", false); ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx2); ASSERT_FALSE(engine.allocateLease4(ctx2)); ASSERT_FALSE(ctx2.old_lease_); EXPECT_EQ(0, getStatistics("v4-allocation-fail")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes")); EXPECT_EQ(0, getStatistics("v4-allocation-fail", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID())); // Client A returns to DHCPDISCOVER and should be offered a lease. // The offered lease address must be different than the one the // Client B has reservation for. AllocEngine::ClientContext4 ctx3(subnet_, clientid_, hwaddr_, IOAddress("192.0.2.101"), false, false, "", true); ctx3.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx3); offered_lease = engine.allocateLease4(ctx3); ASSERT_TRUE(offered_lease); EXPECT_NE(offered_lease->addr_.toText(), "192.0.2.101"); // Client A tries to acquire the lease. It should succeed. At this point // the previous lease should be released and become available for the // Client B. AllocEngine::ClientContext4 ctx4(subnet_, clientid_, hwaddr_, offered_lease->addr_, false, false, "", false); ctx4.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx4); Lease4Ptr allocated_lease = engine.allocateLease4(ctx4); ASSERT_TRUE(allocated_lease); EXPECT_NE(allocated_lease->addr_.toText(), "192.0.2.101"); // Client B tries to get the lease again. It should be offered // a reserved lease. AllocEngine::ClientContext4 ctx5(subnet_, ClientIdPtr(), hwaddr2_, IOAddress("0.0.0.0"), false, false, "", true); ctx5.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx5); offered_lease = engine.allocateLease4(ctx5); ASSERT_TRUE(offered_lease); EXPECT_EQ("192.0.2.101", offered_lease->addr_.toText()); // Client B requests allocation of the lease and it should succeed. AllocEngine::ClientContext4 ctx6(subnet_, ClientIdPtr(), hwaddr2_, offered_lease->addr_, false, false, "", false); ctx6.query_.reset(new Pkt4(DHCPREQUEST, 1234)); allocated_lease = engine.allocateLease4(ctx6); ASSERT_TRUE(allocated_lease); EXPECT_EQ("192.0.2.101", allocated_lease->addr_.toText()); } // This test checks that the address is not assigned from the dynamic // pool if it has been reserved for another client. TEST_F(AllocEngine4Test, reservedAddressVsDynamicPool) { // Create a reservation for the client. HostPtr host(new Host(&hwaddr2_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("192.0.2.100"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); AllocEngine engine(0); // Different client tries to allocate a lease. Note, that we're using // an iterative allocator which would pick the first address from the // dynamic pool, i.e. 192.0.2.100. This address is reserved so we expect // that a different address will be allocated. AllocEngine::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_, IOAddress("0.0.0.0"), false, false, "", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx); Lease4Ptr allocated_lease = engine.allocateLease4(ctx); ASSERT_TRUE(allocated_lease); EXPECT_NE(allocated_lease->addr_.toText(), "192.0.2.100"); Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(allocated_lease->addr_); ASSERT_TRUE(from_mgr); detailCompareLease(allocated_lease, from_mgr); } // This test checks that the client requesting an address which is // reserved for another client will get no lease or a different // address will be assigned if the client is sending a DHCPDISCOVER. TEST_F(AllocEngine4Test, reservedAddressHintUsedByOtherClient) { // Create a reservation for the client. HostPtr host(new Host(&hwaddr2_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("192.0.2.100"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); AllocEngine engine(0); // Different client is requesting this address. AllocEngine::ClientContext4 ctx1(subnet_, ClientIdPtr(), hwaddr_, IOAddress("192.0.2.100"), false, false, "", false); ctx1.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx1); Lease4Ptr allocated_lease = engine.allocateLease4(ctx1); // The client should get no lease (DHCPNAK). ASSERT_FALSE(allocated_lease); EXPECT_EQ(0, getStatistics("v4-allocation-fail")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes")); EXPECT_EQ(0, getStatistics("v4-allocation-fail", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-subnet", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID())); // The same client should get a different lease than requested if // if is sending a DHCPDISCOVER (fake allocation is true). AllocEngine::ClientContext4 ctx2(subnet_, ClientIdPtr(), hwaddr_, IOAddress("192.0.2.100"), false, false, "", true); ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx2); allocated_lease = engine.allocateLease4(ctx2); ASSERT_TRUE(allocated_lease); // Make sure the lease obtained is for a different address. EXPECT_NE(allocated_lease->addr_.toText(), "192.0.2.100"); } // This test checks that the allocation engine refuses to allocate an // address when the pool is exhausted, and the only available // address is reserved for a different client. TEST_F(AllocEngine4Test, reservedAddressShortPool) { AllocEngine engine(0); // Create short pool with only one address. initSubnet(IOAddress("192.0.2.100"), IOAddress("192.0.2.100")); // Reserve the address for a different client. HostPtr host(new Host(&hwaddr2_->hwaddr_[0], hwaddr2_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("192.0.2.100"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); // Allocation engine should determine that the available address is // reserved for someone else and not allocate it. AllocEngine::ClientContext4 ctx1(subnet_, ClientIdPtr(), hwaddr_, IOAddress("0.0.0.0"), false, false, "", false); ctx1.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx1); Lease4Ptr allocated_lease = engine.allocateLease4(ctx1); ASSERT_FALSE(allocated_lease); EXPECT_EQ(1, getStatistics("v4-allocation-fail")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network")); EXPECT_EQ(1, getStatistics("v4-allocation-fail-subnet")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools")); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes")); EXPECT_EQ(1, getStatistics("v4-allocation-fail", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-shared-network", subnet_->getID())); EXPECT_EQ(1, getStatistics("v4-allocation-fail-subnet", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-no-pools", subnet_->getID())); EXPECT_EQ(0, getStatistics("v4-allocation-fail-classes", subnet_->getID())); // Now, let's remove the reservation. initSubnet(IOAddress("192.0.2.100"), IOAddress("192.0.2.100")); CfgMgr::instance().commit(); // Address should be successfully allocated. AllocEngine::ClientContext4 ctx2(subnet_, ClientIdPtr(), hwaddr_, IOAddress("0.0.0.0"), false, false, "", false); ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx2); allocated_lease = engine.allocateLease4(ctx2); ASSERT_TRUE(allocated_lease); EXPECT_EQ("192.0.2.100", allocated_lease->addr_.toText()); } // This test checks that the AllocEngine allocates an address from the // dynamic pool if the client's reservation is made for a hostname but // not for an address. TEST_F(AllocEngine4Test, reservedHostname) { AllocEngine engine(0); // Create a reservation for a hostname. Address is set to 0 which // indicates that there is no reservation. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress::IPV4_ZERO_ADDRESS(), "foo.example.org")); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); // Try to allocate a lease. AllocEngine::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_, IOAddress("192.0.2.109"), false, false, "foo.example.org", true); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx); Lease4Ptr allocated_lease = engine.allocateLease4(ctx); ASSERT_TRUE(allocated_lease); ASSERT_FALSE(allocated_lease->addr_.isV4Zero()); ASSERT_EQ("192.0.2.109", allocated_lease->addr_.toText()); ctx.requested_address_ = allocated_lease->addr_; ctx.fake_allocation_ = false; allocated_lease = engine.allocateLease4(ctx); ASSERT_TRUE(allocated_lease); EXPECT_EQ("192.0.2.109", allocated_lease->addr_.toText()); } // This test checks that the AllocEngine::findReservation method finds // and returns host reservation for the DHCPv4 client using the data from // the client context. If the host reservation can't be found, it sets // the value of NULL in the host_ field of the client context. TEST_F(AllocEngine4Test, findReservation) { // Create the instance of the allocation engine. AllocEngine engine(0); // Context is required to call the AllocEngine::findReservation. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "", false); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); ctx.addHostIdentifier(Host::IDENT_HWADDR, hwaddr_->hwaddr_); ctx.addHostIdentifier(Host::IDENT_DUID, clientid_->getClientId()); // There is no reservation in the database so no host should be returned. ASSERT_NO_THROW(engine.findReservation(ctx)); EXPECT_FALSE(ctx.currentHost()); // Create a reservation for the client. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("192.0.2.100"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); // This time the reservation should be returned. ctx.hosts_.clear(); ASSERT_NO_THROW(engine.findReservation(ctx)); EXPECT_TRUE(ctx.currentHost()); EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation()); // It shouldn't be returned when reservations-in-subnet is disabled. subnet_->setReservationsInSubnet(false); ctx.hosts_.clear(); ASSERT_NO_THROW(engine.findReservation(ctx)); EXPECT_FALSE(ctx.currentHost()); // Check the reservations-in-subnet and reservations-out-of-pool flags. subnet_->setReservationsInSubnet(true); subnet_->setReservationsOutOfPool(true); ctx.hosts_.clear(); ASSERT_NO_THROW(engine.findReservation(ctx)); EXPECT_TRUE(ctx.currentHost()); EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation()); // This time use the client identifier to search for the host. host.reset(new Host(&clientid_->getClientId()[0], clientid_->getClientId().size(), Host::IDENT_DUID, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("192.0.2.101"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); ctx.hosts_.clear(); ASSERT_NO_THROW(engine.findReservation(ctx)); EXPECT_TRUE(ctx.currentHost()); EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation()); // Remove the subnet. Subnet id is required to find host reservations, so // if it is set to NULL, no reservation should be returned ctx.subnet_.reset(); ctx.hosts_.clear(); ASSERT_NO_THROW(engine.findReservation(ctx)); EXPECT_FALSE(ctx.currentHost()); // The same if there is a mismatch of the subnet id between the reservation // and the context. ctx.subnet_ = subnet_; host.reset(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID() + 1, SUBNET_ID_UNUSED, IOAddress("192.0.2.100"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); ctx.hosts_.clear(); ASSERT_NO_THROW(engine.findReservation(ctx)); EXPECT_FALSE(ctx.currentHost()); } // This test checks if the simple IPv4 allocation can succeed and that // statistic for allocated addresses is increased appropriately. TEST_F(AllocEngine4Test, simpleAlloc4Stats) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, true, "somehost.example.com.", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); // Let's pretend 100 addresses were allocated already string name = StatsMgr::generateName("subnet", subnet_->getID(), "assigned-addresses"); StatsMgr::instance().addValue(name, static_cast(100)); int64_t cumulative = getStatistics("cumulative-assigned-addresses", subnet_->getID()); int64_t glbl_cumulative = getStatistics("cumulative-assigned-addresses"); Lease4Ptr lease = engine->allocateLease4(ctx); // The new lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // Check that we got a lease ASSERT_TRUE(lease); // The statistic should be there and it should be increased by 1 (to 101). ObservationPtr stat = StatsMgr::instance().getObservation(name); ASSERT_TRUE(stat); EXPECT_EQ(101, stat->getInteger().first); cumulative += 1; EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", cumulative, subnet_->getID())); glbl_cumulative += 1; EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", glbl_cumulative)); } // This test checks if the fake allocation (for DHCPDISCOVER) can succeed // and that it doesn't increase allocated-addresses statistic. TEST_F(AllocEngine4Test, fakeAlloc4Stat) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(100))); ASSERT_TRUE(engine); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, true, "host.example.com.", true); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); // Let's pretend 100 addresses were allocated already string name = StatsMgr::generateName("subnet", subnet_->getID(), "assigned-addresses"); StatsMgr::instance().addValue(name, static_cast(100)); int64_t cumulative = getStatistics("cumulative-assigned-addresses", subnet_->getID()); int64_t glbl_cumulative = getStatistics("cumulative-assigned-addresses"); Lease4Ptr lease = engine->allocateLease4(ctx); // The new lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // Check that we got a lease ASSERT_TRUE(lease); // The statistic should be there and it should not be increased // (should be still equal to 100). ObservationPtr stat = StatsMgr::instance().getObservation(name); ASSERT_TRUE(stat); EXPECT_EQ(100, stat->getInteger().first); EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", cumulative, subnet_->getID())); EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", glbl_cumulative)); } // This test checks that the allocated-addresses statistic is decreased when // the client has a lease and a reservation for a different address is // available. TEST_F(AllocEngine4Test, reservedAddressExistingLeaseStat) { // Create the reservation for the client. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("192.0.2.123"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); // Create a lease for the client with a different address than the reserved // one. Lease4Ptr lease(new Lease4(IOAddress("192.0.2.101"), hwaddr_, &clientid_->getClientId()[0], clientid_->getClientId().size(), 100, time(NULL), subnet_->getID(), false, false, "")); LeaseMgrFactory::instance().addLease(lease); AllocEngine engine(100); // Let's pretend 100 addresses were allocated already string name = StatsMgr::generateName("subnet", subnet_->getID(), "assigned-addresses"); StatsMgr::instance().addValue(name, static_cast(100)); int64_t cumulative = getStatistics("cumulative-assigned-addresses", subnet_->getID()); int64_t glbl_cumulative = getStatistics("cumulative-assigned-addresses"); // Request allocation of the reserved address. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("192.0.2.123"), false, false, "", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx); Lease4Ptr allocated_lease = engine.allocateLease4(ctx); ASSERT_TRUE(allocated_lease); // The statistic should be still at 100. Note that it was decreased // (because old lease was removed), but also increased (because the // new lease was immediately allocated). ObservationPtr stat = StatsMgr::instance().getObservation(name); ASSERT_TRUE(stat); EXPECT_EQ(100, stat->getInteger().first); cumulative += 1; EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", cumulative, subnet_->getID())); glbl_cumulative += 1; EXPECT_TRUE(testStatistics("cumulative-assigned-addresses", glbl_cumulative)); // Lets' double check that the actual allocation took place. EXPECT_FALSE(ctx.fake_allocation_); } // This test checks the behavior of the allocation engine in the following // scenario: // - Client has no lease in the database. // - Client has a global reservation outside of the subnet. // - Client sends DISCOVER // - Client is allocated an address within the subnet. // - Lease is not added to the lease database TEST_F(AllocEngine4Test, globalReservationReservedNonMatchingAddressDiscover) { // Create reservation for the client. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, SUBNET_ID_GLOBAL, SUBNET_ID_UNUSED, IOAddress("192.0.77.123"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); AllocEngine engine(0); subnet_->setReservationsGlobal(true); // Query allocation engine for the lease to be assigned to this // client without specifying the address to be assigned. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "", true); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); // Look up the host. AllocEngine::findReservation(ctx); // We should have the correct current host EXPECT_TRUE(ctx.currentHost()); EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname()); EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation()); // We should allocate an address in the subnet Lease4Ptr lease = engine.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_TRUE(subnet_->inRange(lease->addr_)); // This is a "fake" allocation so the returned lease should not be committed // to the lease database. EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_)); // Client had no lease in the database, so the old lease returned should // be NULL. ASSERT_FALSE(ctx.old_lease_); } // This test checks the behavior of the allocation engine in the following // scenario: // - Client has no lease in the database. // - Client has a global reservation matching the current subnet. // - Client sends DISCOVER // - Client is allocated the reserved address. // - Lease is not added to the lease database TEST_F(AllocEngine4Test, globalReservationReservedMatchingAddressDiscover) { // Create reservation for the client. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, SUBNET_ID_GLOBAL, SUBNET_ID_UNUSED, IOAddress("192.0.2.10"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); AllocEngine engine(0); subnet_->setReservationsGlobal(true); // Query allocation engine for the lease to be assigned to this // client without specifying the address to be assigned. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "", true); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); // Look up the host. AllocEngine::findReservation(ctx); // We should have the correct current host EXPECT_TRUE(ctx.currentHost()); EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname()); EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation()); // We should allocate the reserved address. Lease4Ptr lease = engine.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("192.0.2.10", lease->addr_.toText()); // This is a "fake" allocation so the returned lease should not be committed // to the lease database. EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_)); // Client had no lease in the database, so the old lease returned should // be NULL. ASSERT_FALSE(ctx.old_lease_); } // This test checks the behavior of the allocation engine in the following // scenario: // - Client has no lease in the database. // - Client has a global reservation outside the current subnet. // - Client sends REQUEST // - Client is allocated a dynamic address. // - Lease is added to the lease database TEST_F(AllocEngine4Test, globalReservationReservedNonMatchingAddressRequest) { // Create reservation for the client. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, SUBNET_ID_GLOBAL, SUBNET_ID_UNUSED, IOAddress("192.0.77.123"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); AllocEngine engine(0); subnet_->setReservationsGlobal(true); // Query allocation engine for the lease to be assigned to this // client without specifying the address to be assigned. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); // Look up the host. AllocEngine::findReservation(ctx); // We should have the correct current host EXPECT_TRUE(ctx.currentHost()); EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname()); EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation()); // We should allocate the reserved address. Lease4Ptr lease = engine.allocateLease4(ctx); ASSERT_TRUE(lease); ASSERT_NE("192.0.77.123", lease->addr_.toText()); EXPECT_TRUE(subnet_->inRange(lease->addr_)); // Check that the lease is indeed in LeaseMgr Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); // Now check that the lease in LeaseMgr has the same parameters detailCompareLease(lease, from_mgr); // Client had no lease in the database, so the old lease returned should // be NULL. ASSERT_FALSE(ctx.old_lease_); } // This test checks the behavior of the allocation engine in the following // scenario: // - Client has no lease in the database. // - Client has a global reservation matching the current subnet. // - Client sends REQUEST // - Client is allocated the reserved address. // - Lease is added to the lease database TEST_F(AllocEngine4Test, globalReservationReservedMatchingAddressRequest) { // Create reservation for the client. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, SUBNET_ID_GLOBAL, SUBNET_ID_UNUSED, IOAddress("192.0.2.10"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); AllocEngine engine(0); subnet_->setReservationsGlobal(true); // Query allocation engine for the lease to be assigned to this // client without specifying the address to be assigned. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); // Look up the host. AllocEngine::findReservation(ctx); // We should have the correct current host EXPECT_TRUE(ctx.currentHost()); EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname()); EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation()); // We should allocate the reserved address. Lease4Ptr lease = engine.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("192.0.2.10", lease->addr_.toText()); // Check that the lease is indeed in LeaseMgr Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); // Now check that the lease in LeaseMgr has the same parameters detailCompareLease(lease, from_mgr); // Client had no lease in the database, so the old lease returned should // be NULL. ASSERT_FALSE(ctx.old_lease_); } // This test checks the behavior of the allocation engine in the following // scenario: // - Client has no lease in the database. // - Client has a global reservation. // - Client sends DISCOVER // - Client is allocated a dynamic address from matched subnet // - Lease is not added to the lease database TEST_F(AllocEngine4Test, globalReservationDynamicDiscover) { // Create reservation for the client. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, SUBNET_ID_GLOBAL, SUBNET_ID_UNUSED, IOAddress::IPV4_ZERO_ADDRESS(), "foo.example.org")); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); AllocEngine engine(0); subnet_->setReservationsGlobal(true); // Query allocation engine for the lease to be assigned to this // client without specifying the address to be assigned. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "", true); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); // Look up the host. AllocEngine::findReservation(ctx); // We should have the correct current host EXPECT_TRUE(ctx.currentHost()); EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname()); EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation()); // We should allocate a dynamic address. Lease4Ptr lease = engine.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("192.0.2.100", lease->addr_.toText()); // This is a "fake" allocation so the returned lease should not be committed // to the lease database. EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_)); // Client had no lease in the database, so the old lease returned should // be NULL. ASSERT_FALSE(ctx.old_lease_); } // This test checks the behavior of the allocation engine in the following // scenario: // - Client has no lease in the database. // - Client has a global reservation. // - Client sends REQUEST // - Client is allocated a dynamic address from matched subnet // - Lease is added to the lease database TEST_F(AllocEngine4Test, globalReservationDynamicRequest) { // Create reservation for the client. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, SUBNET_ID_GLOBAL, SUBNET_ID_UNUSED, IOAddress::IPV4_ZERO_ADDRESS(), "foo.example.org")); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); AllocEngine engine(0); subnet_->setReservationsGlobal(true); // Query allocation engine for the lease to be assigned to this // client without specifying the address to be assigned. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); // Look up the host. AllocEngine::findReservation(ctx); // We should have the correct current host EXPECT_TRUE(ctx.currentHost()); EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname()); EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation()); // We should allocate a dynamic address. Lease4Ptr lease = engine.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("192.0.2.100", lease->addr_.toText()); // Check that the lease is indeed in LeaseMgr Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); // Now check that the lease in LeaseMgr has the same parameters detailCompareLease(lease, from_mgr); // Client had no lease in the database, so the old lease returned should // be NULL. ASSERT_FALSE(ctx.old_lease_); } // This test checks the behavior of the allocation engine in the following // scenario: // - Client has no lease in the database. // - Client has a subnet reservation. // - Client sends DISCOVER // - Client is allocated the reserved address. // - Lease is not added to the lease database TEST_F(AllocEngine4Test, mixedReservationReservedAddressDiscover) { // Create reservation for the client. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("192.0.2.123"), "foo.example.org")); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); AllocEngine engine(0); subnet_->setReservationsGlobal(true); subnet_->setReservationsInSubnet(true); // Query allocation engine for the lease to be assigned to this // client without specifying the address to be assigned. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "", true); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); // Look up the host. AllocEngine::findReservation(ctx); // We should have the correct current host EXPECT_TRUE(ctx.currentHost()); EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname()); EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation()); // We should allocate the reserved address. Lease4Ptr lease = engine.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("192.0.2.123", lease->addr_.toText()); // This is a "fake" allocation so the returned lease should not be committed // to the lease database. EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_)); // Client had no lease in the database, so the old lease returned should // be NULL. ASSERT_FALSE(ctx.old_lease_); } // This test checks the behavior of the allocation engine in the following // scenario: // - Client has no lease in the database. // - Client has a subnet reservation. // - Client sends REQUEST // - Client is allocated the reserved address. // - Lease is added to the lease database TEST_F(AllocEngine4Test, mixedReservationReservedAddressRequest) { // Create reservation for the client. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("192.0.2.123"), "foo.example.org")); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); AllocEngine engine(0); subnet_->setReservationsGlobal(true); subnet_->setReservationsInSubnet(true); // Query allocation engine for the lease to be assigned to this // client without specifying the address to be assigned. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); // Look up the host. AllocEngine::findReservation(ctx); // We should have the correct current host EXPECT_TRUE(ctx.currentHost()); EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname()); EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation()); // We should allocate the reserved address. Lease4Ptr lease = engine.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("192.0.2.123", lease->addr_.toText()); // Check that the lease is indeed in LeaseMgr Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); // Now check that the lease in LeaseMgr has the same parameters detailCompareLease(lease, from_mgr); // Client had no lease in the database, so the old lease returned should // be NULL. ASSERT_FALSE(ctx.old_lease_); } // This test checks the behavior of the allocation engine in the following // scenario: // - Client has no lease in the database. // - Client has a global and a subnet reservation. // - Client sends DISCOVER // - Client is allocated the reserved address. // - Lease is not added to the lease database TEST_F(AllocEngine4Test, bothReservationReservedAddressDiscover) { // Create reservations for the client. HostPtr ghost(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, SUBNET_ID_GLOBAL, SUBNET_ID_UNUSED, IOAddress("192.0.77.123"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(ghost); HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("192.0.2.123"), "foo.example.org")); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); AllocEngine engine(0); subnet_->setReservationsGlobal(true); subnet_->setReservationsInSubnet(true); // Query allocation engine for the lease to be assigned to this // client without specifying the address to be assigned. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "", true); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); // Look up the host. AllocEngine::findReservation(ctx); // We should have the correct current host EXPECT_TRUE(ctx.currentHost()); EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname()); EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation()); // We should allocate the reserved address. Lease4Ptr lease = engine.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("192.0.2.123", lease->addr_.toText()); // This is a "fake" allocation so the returned lease should not be committed // to the lease database. EXPECT_FALSE(LeaseMgrFactory::instance().getLease4(lease->addr_)); // Client had no lease in the database, so the old lease returned should // be NULL. ASSERT_FALSE(ctx.old_lease_); } // This test checks the behavior of the allocation engine in the following // scenario: // - Client has no lease in the database. // - Client has a global and a subnet reservation. // - Client sends REQUEST // - Client is allocated the reserved address. // - Lease is added to the lease database TEST_F(AllocEngine4Test, bothReservationReservedAddressRequest) { // Create reservations for the client. HostPtr ghost(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, SUBNET_ID_GLOBAL, SUBNET_ID_UNUSED, IOAddress("192.0.77.123"))); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(ghost); HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("192.0.2.123"), "foo.example.org")); CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); CfgMgr::instance().commit(); AllocEngine engine(0); subnet_->setReservationsGlobal(true); subnet_->setReservationsInSubnet(true); // Query allocation engine for the lease to be assigned to this // client without specifying the address to be assigned. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); // Look up the host. AllocEngine::findReservation(ctx); // We should have the correct current host EXPECT_TRUE(ctx.currentHost()); EXPECT_EQ(ctx.currentHost()->getHostname(), host->getHostname()); EXPECT_EQ(ctx.currentHost()->getIPv4Reservation(), host->getIPv4Reservation()); // We should allocate the reserved address. Lease4Ptr lease = engine.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("192.0.2.123", lease->addr_.toText()); // Check that the lease is indeed in LeaseMgr Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); // Now check that the lease in LeaseMgr has the same parameters detailCompareLease(lease, from_mgr); // Client had no lease in the database, so the old lease returned should // be NULL. ASSERT_FALSE(ctx.old_lease_); } // Exercises AllocEnginer4Test::updateExtendedInfo4() through various // permutations of client packet content. TEST_F(AllocEngine4Test, updateExtendedInfo4) { // 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::string rai_data_; // RAI option the client packet contains std::string exp_context_json_; // expected user context on the lease bool exp_ret; // expected returned value }; // Test scenarios. std::vector scenarios { { "no context, no rai", "", "", "", false }, { "some original context, no rai", "{\"foo\": 123}", "", "{\"foo\": 123}", false }, { "no original context, rai", "", "0x52060104aabbccdd", "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":" " \"0x0104AABBCCDD\" } } }", true }, { "no original context, rai, remote and relay ids", "", "0x520a02030102030c03aabbcc", "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":" " \"0x02030102030C03AABBCC\", \"remote-id\": \"010203\"," " \"relay-id\": \"AABBCC\" } } }", true }, { "some original context, rai", "{\"foo\": 123, \"ISC\":{\"bar\": 456}}", "0x52060104aabbccdd", "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":" " \"0x0104AABBCCDD\" }, \"bar\": 456 }, \"foo\": 123 }", true }, { "bad original context, rai", "[\"foo\"]", "0x52060104aabbccdd", "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":" " \"0x0104AABBCCDD\" } } }", true }, { "some original context with bad isc entry, rai", "{\"foo\": 123, \"ISC\":[\"bar\"]}", "0x52060104aabbccdd", "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":" " \"0x0104AABBCCDD\" } }, \"foo\": 123 }", true }, { "some original context, rai, remote and relay ids", "{\"foo\": 123, \"ISC\":{ \"bar\": 456}}", "0x520a02030102030c03aabbcc", "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":" " \"0x02030102030C03AABBCC\", \"remote-id\": \"010203\"," " \"relay-id\": \"AABBCC\" }, \"bar\": 456 }, \"foo\": 123 }", true }, { "original rai context, no rai", "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":" " \"0x0104AABBCCDD\" } } }", "", "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":" " \"0x0104AABBCCDD\" } } }", false }, { "original rai context, different rai", "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":" " \"0x0104AABBCCDD\" } } }", "0x52060104ddeeffaa", "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":" " \"0x0104DDEEFFAA\" } } }", true }, { "original rai context, different rai, remote and relay ids", "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":" " \"0x0104AABBCCDD\" } } }", "0x520a02030102030c03aabbcc", "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":" " \"0x02030102030C03AABBCC\", \"remote-id\": \"010203\"," " \"relay-id\": \"AABBCC\" } } }", true }}; // Create the allocation engine, context and lease. NakedAllocEngine engine(0); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(), false, false, "", true); // All scenarios require storage to be enabled. ctx.subnet_->setStoreExtendedInfo(true); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); Lease4Ptr lease = engine.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("192.0.2.100", lease->addr_.toText()); // 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()))); } // Create the client packet and the add RAI option (if one). ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); if (!scenario.rai_data_.empty()) { std::vector opt_data; ASSERT_NO_THROW(util::str::decodeFormattedHexString(scenario.rai_data_, opt_data)) << "scenario.rai_data_ is invalid, test is broken"; OptionDefinitionPtr rai_def = LibDHCP::getOptionDef(DHCP4_OPTION_SPACE, DHO_DHCP_AGENT_OPTIONS); ASSERT_TRUE(rai_def) << "could not get RAI definition, test is broken"; ASSERT_GT(opt_data.size(), 2); ASSERT_EQ(DHO_DHCP_AGENT_OPTIONS, opt_data[0]); ASSERT_EQ(opt_data[1] + 2, opt_data.size()); std::vector rai_data(opt_data.cbegin() + 2, opt_data.cend()); OptionCustomPtr rai; ASSERT_NO_THROW(rai.reset(new OptionCustom(*rai_def, Option::V4, rai_data))) << "could not create rai option, test is broken"; ctx.query_->addOption(rai); } // Call AllocEngine::updateLease4ExtendeInfo(). bool ret = false; ASSERT_NO_THROW_LOG(ret = engine.callUpdateLease4ExtendedInfo(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 (e.g. RAI option for now) is // added to a V4 lease when leases are created and/or renewed, // when store-extended-info is true. TEST_F(AllocEngine4Test, storeExtendedInfoEnabled4) { // Structure that defines a test scenario. struct Scenario { std::string description_; // test description std::vector mac_; // MAC address std::string rai_data_; // RAI option the client packet contains std::string exp_context_json_; // expected user context on the lease std::string exp_address_; // expected lease address }; std::vector mac1 = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0x01 }; std::string mac1_addr = "192.0.2.100"; std::vector mac2 = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0x02 }; std::string mac2_addr = "192.0.2.101"; // Test scenarios. std::vector scenarios { { "create client one without rai", mac1, "", "", mac1_addr }, { "renew client one without rai", {}, "", "", mac1_addr }, { "create client two with rai", mac2, "0x52050104a1b1c1d1", "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":" " \"0x52050104A1B1C1D1\" } } }", mac2_addr }, { "renew client two without rai", {}, "", "{ \"ISC\": { \"relay-agent-info\": { \"sub-options\":" " \"0x52050104A1B1C1D1\" } } }", mac2_addr }}; // Create the allocation engine, context and lease. NakedAllocEngine engine(0); // All of the scenarios require storage to be enabled. subnet_->setStoreExtendedInfo(true); AllocEngine::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(), false, false, "", false); // Iterate over the test scenarios. Lease4Ptr lease; 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"; } // If we have a MAC address this scenario is for a new client. if (!scenario.mac_.empty()) { std::cout << "setting mac address" << std::endl; ASSERT_NO_THROW(ctx.hwaddr_.reset(new HWAddr(scenario.mac_, HTYPE_ETHER))) << "invalid MAC address, test is broken"; } // Create the client packet and the add RAI option (if one). ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); if (!scenario.rai_data_.empty()) { std::vector opt_data; ASSERT_NO_THROW(util::str::decodeFormattedHexString(scenario.rai_data_, opt_data)) << "scenario.rai_data_ is invalid, test is broken"; OptionPtr rai; ASSERT_NO_THROW(rai.reset(new Option(Option::V4, 0x52, opt_data))) << "could not create rai option, test is broken"; ctx.query_->addOption(rai); } // Create or renew the lease. Lease4Ptr lease = engine.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ(scenario.exp_address_, lease->addr_.toText()); // 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 (e.g. RAI option for now) is // not added to a V4 lease when leases are created and/or renewed, // when store-extended-info is false. TEST_F(AllocEngine4Test, storeExtendedInfoDisabled4) { // Structure that defines a test scenario. struct Scenario { std::string description_; // test description std::vector mac_; // MAC address std::string rai_data_; // RAI option the client packet contains std::string exp_address_; // expected lease address }; std::vector mac1 = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0x01 }; std::string mac1_addr = "192.0.2.100"; std::vector mac2 = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0x02 }; std::string mac2_addr = "192.0.2.101"; // Test scenarios. std::vector scenarios { { "create client one without rai", mac1, "", mac1_addr }, { "renew client one without rai", {}, "", mac1_addr }, { "create client two with rai", mac2, "0x52050104a1b1c1d1", mac2_addr }, { "renew client two with rai", {}, "0x52050104a1b1c1d1", mac2_addr }}; // Create the allocation engine, context and lease. NakedAllocEngine engine(0); // All of the scenarios require storage to be disabled. subnet_->setStoreExtendedInfo(false); AllocEngine::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_, IOAddress::IPV4_ZERO_ADDRESS(), false, false, "", false); Lease4Ptr lease; // Iterate over the test scenarios. for (auto scenario : scenarios) { SCOPED_TRACE(scenario.description_); // If we have a MAC address this scenario is for a new client. if (!scenario.mac_.empty()) { std::cout << "setting mac address" << std::endl; ASSERT_NO_THROW(ctx.hwaddr_.reset(new HWAddr(scenario.mac_, HTYPE_ETHER))) << "invalid MAC address, test is broken"; } // Create the client packet and the add RAI option (if one). ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); if (!scenario.rai_data_.empty()) { std::vector opt_data; ASSERT_NO_THROW(util::str::decodeFormattedHexString(scenario.rai_data_, opt_data)) << "scenario.rai_data_ is invalid, test is broken"; OptionPtr rai; ASSERT_NO_THROW(rai.reset(new Option(Option::V4, 0x52, opt_data))) << "could not create rai option, test is broken"; ctx.query_->addOption(rai); } // Create or renew the lease. Lease4Ptr lease = engine.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ(scenario.exp_address_, lease->addr_.toText()); // Verify the lease does not have user context content. ASSERT_FALSE(lease->getContext()); } } // This test checks if a lease can be reused in DHCPDISCOVER (fake allocation) // using cache threshold. TEST_F(AllocEngine4Test, discoverCacheThreshold4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); // Set valid lifetime to 500. uint32_t valid = 500; subnet_->setValid(valid); // Set the threshold to 25%. subnet_->setCacheThreshold(.25); IOAddress addr("192.0.2.105"); time_t now = time(NULL) - 100; // Allocated 100 seconds ago. Lease4Ptr lease(new Lease4(addr, hwaddr_, clientid_, valid, now, subnet_->getID())); ASSERT_FALSE(lease->expired()); ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); // Create a context for fake allocation. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, addr, false, false, "", true); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); lease = engine->allocateLease4(ctx); // Check that we got that single lease. ASSERT_TRUE(lease); EXPECT_EQ(addr, lease->addr_); // The lease was reused. time_t age = lease->cltt_ - now; EXPECT_GE(age, 100); EXPECT_LE(age, 110); EXPECT_EQ(valid - age, lease->reuseable_valid_lft_); // Check other lease parameters. EXPECT_EQ(lease->subnet_id_, subnet_->getID()); ASSERT_TRUE(lease->client_id_); EXPECT_TRUE(*lease->client_id_ == *clientid_); ASSERT_TRUE(lease->hwaddr_); EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_); } // This test checks if a lease can be reused in DHCPREQUEST (real allocation) // using cache threshold. TEST_F(AllocEngine4Test, requestCacheThreshold4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); // Set valid lifetime to 500. uint32_t valid = 500; subnet_->setValid(valid); // Set the threshold to 25%. subnet_->setCacheThreshold(.25); IOAddress addr("192.0.2.105"); time_t now = time(NULL) - 100; // Allocated 100 seconds ago. Lease4Ptr lease(new Lease4(addr, hwaddr_, clientid_, valid, now, subnet_->getID())); ASSERT_FALSE(lease->expired()); // Copy the lease, so as it can be compared with. Lease4Ptr original_lease(new Lease4(*lease)); ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); // Create a context for real allocation. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, addr, false, false, "", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); lease = engine->allocateLease4(ctx); // Check that we got that single lease. ASSERT_TRUE(lease); EXPECT_EQ(addr, lease->addr_); // The lease was reused. time_t age = lease->cltt_ - now; EXPECT_GE(age, 100); EXPECT_LE(age, 110); EXPECT_EQ(valid - age, lease->reuseable_valid_lft_); // Check other lease parameters. EXPECT_EQ(lease->subnet_id_, subnet_->getID()); ASSERT_TRUE(lease->client_id_); EXPECT_TRUE(*lease->client_id_ == *clientid_); ASSERT_TRUE(lease->hwaddr_); EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_); // Check the lease was not updated in the database. Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr); ASSERT_TRUE(from_mgr); detailCompareLease(original_lease, from_mgr); } /// We proved that there is no different from the "cache" feature between /// discovers and request at the exception of the lease database update. // This test checks if a lease can be reused in DHCPDISCOVER (fake allocation) // using cache max age. TEST_F(AllocEngine4Test, discoverCacheMaxAge4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); // Set valid lifetime to 500. uint32_t valid = 500; subnet_->setValid(valid); // Set the max age to 200. subnet_->setCacheMaxAge(200); IOAddress addr("192.0.2.105"); time_t now = time(NULL) - 100; // Allocated 100 seconds ago. Lease4Ptr lease(new Lease4(addr, hwaddr_, clientid_, valid, now, subnet_->getID())); ASSERT_FALSE(lease->expired()); ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); // Create a context for fake allocation. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, addr, false, false, "", true); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); lease = engine->allocateLease4(ctx); // Check that we got that single lease. ASSERT_TRUE(lease); EXPECT_EQ(addr, lease->addr_); // The lease was reused. time_t age = lease->cltt_ - now; EXPECT_GE(age, 100); EXPECT_LE(age, 110); EXPECT_EQ(valid - age, lease->reuseable_valid_lft_); // Check other lease parameters. EXPECT_EQ(lease->subnet_id_, subnet_->getID()); ASSERT_TRUE(lease->client_id_); EXPECT_TRUE(*lease->client_id_ == *clientid_); ASSERT_TRUE(lease->hwaddr_); EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_); } // This test checks if a lease can be reused in DHCPREQUEST (real allocation) // using both cache threshold and max age. TEST_F(AllocEngine4Test, requestCacheBoth4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); // Set valid lifetime to 500. uint32_t valid = 500; subnet_->setValid(valid); // Set the threshold to 25%. subnet_->setCacheThreshold(.25); // Set the max age to 200. subnet_->setCacheMaxAge(200); IOAddress addr("192.0.2.105"); time_t now = time(NULL) - 100; // Allocated 100 seconds ago. Lease4Ptr lease(new Lease4(addr, hwaddr_, clientid_, valid, now, subnet_->getID())); ASSERT_FALSE(lease->expired()); // Copy the lease, so as it can be compared with. Lease4Ptr original_lease(new Lease4(*lease)); ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); // Create a context for real allocation. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, addr, false, false, "", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); lease = engine->allocateLease4(ctx); // Check that we got that single lease. ASSERT_TRUE(lease); EXPECT_EQ(addr, lease->addr_); // The lease was reused. time_t age = lease->cltt_ - now; EXPECT_GE(age, 100); EXPECT_LE(age, 110); EXPECT_EQ(valid - age, lease->reuseable_valid_lft_); // Check other lease parameters. EXPECT_EQ(lease->subnet_id_, subnet_->getID()); ASSERT_TRUE(lease->client_id_); EXPECT_TRUE(*lease->client_id_ == *clientid_); ASSERT_TRUE(lease->hwaddr_); EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_); // Check the lease was not updated in the database. Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr); ASSERT_TRUE(from_mgr); detailCompareLease(original_lease, from_mgr); } // This test checks if a lease can't be reused in DHCPDISCOVER (fake allocation) // using too small cache threshold. TEST_F(AllocEngine4Test, discoverCacheBadThreshold4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); // Set valid lifetime to 500. uint32_t valid = 500; subnet_->setValid(valid); // Set the threshold to 10%. subnet_->setCacheThreshold(.10); IOAddress addr("192.0.2.105"); time_t now = time(NULL) - 100; // Allocated 100 seconds ago. Lease4Ptr lease(new Lease4(addr, hwaddr_, clientid_, valid, now, subnet_->getID())); ASSERT_FALSE(lease->expired()); ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); // Create a context for fake allocation. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, addr, false, false, "", true); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); lease = engine->allocateLease4(ctx); // Check that we got that single lease. ASSERT_TRUE(lease); EXPECT_EQ(addr, lease->addr_); // The lease was not reused. EXPECT_EQ(0, lease->reuseable_valid_lft_); } // This test checks if a lease can't be reused in DHCPREQUEST (real allocation) // using too small cache max age. TEST_F(AllocEngine4Test, requestCacheBadMaxAge4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); // Set valid lifetime to 500. uint32_t valid = 500; subnet_->setValid(valid); // Set the threshold to 25%. subnet_->setCacheThreshold(.25); // Set the max age to 50. subnet_->setCacheMaxAge(50); IOAddress addr("192.0.2.105"); time_t now = time(NULL) - 100; // Allocated 100 seconds ago. Lease4Ptr lease(new Lease4(addr, hwaddr_, clientid_, valid, now, subnet_->getID())); ASSERT_FALSE(lease->expired()); // Create a context for real allocation. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, addr, false, false, "", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); lease = engine->allocateLease4(ctx); // Check that we got that single lease. ASSERT_TRUE(lease); EXPECT_EQ(addr, lease->addr_); // The lease was not reused. EXPECT_EQ(0, lease->reuseable_valid_lft_); // Check the lease was updated in the database. Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr); ASSERT_TRUE(from_mgr); detailCompareLease(lease, from_mgr); } // This test checks if a lease can't be reused in DHCPDISCOVER (fake allocation) // when the valid lifetime was reduced. TEST_F(AllocEngine4Test, discoverCacheReducedValid4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); // Set valid lifetime to 200. subnet_->setValid(200); // Set the threshold to 25%. subnet_->setCacheThreshold(.25); IOAddress addr("192.0.2.105"); time_t now = time(NULL) - 100; // Allocated 100 seconds ago. uint32_t valid = 500; // Used a value greater than subnet_->getValid(). Lease4Ptr lease(new Lease4(addr, hwaddr_, clientid_, valid, now, subnet_->getID())); ASSERT_FALSE(lease->expired()); ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); // Create a context for fake allocation. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, addr, false, false, "", true); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); lease = engine->allocateLease4(ctx); // Check that we got that single lease. ASSERT_TRUE(lease); EXPECT_EQ(addr, lease->addr_); // The lease was not reused. EXPECT_EQ(0, lease->reuseable_valid_lft_); } // This test checks if a lease can't be reused in DHCPREQUEST (real allocation) // when DDNS parameter changed. TEST_F(AllocEngine4Test, requestCacheFwdDDNS4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); // Set valid lifetime to 500. uint32_t valid = 500; subnet_->setValid(valid); // Set the max age to 200. subnet_->setCacheMaxAge(200); IOAddress addr("192.0.2.105"); time_t now = time(NULL) - 100; // Allocated 100 seconds ago. Lease4Ptr lease(new Lease4(addr, hwaddr_, clientid_, valid, now, subnet_->getID())); ASSERT_FALSE(lease->expired()); // Create a context for real allocation with fwd_dns_update changed. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, addr, true, false, "", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); lease = engine->allocateLease4(ctx); // Check that we got that single lease. ASSERT_TRUE(lease); EXPECT_EQ(addr, lease->addr_); // The lease was not reused. EXPECT_EQ(0, lease->reuseable_valid_lft_); // Check the lease was updated in the database. Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr); ASSERT_TRUE(from_mgr); detailCompareLease(lease, from_mgr); } // This test checks if a lease can't be reused in DHCPDISCOVER (fake allocation) // when DDNS parameter changed. TEST_F(AllocEngine4Test, discoverCacheRevDDNS4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); // Set valid lifetime to 500. uint32_t valid = 500; subnet_->setValid(valid); // Set the threshold to 25%. subnet_->setCacheThreshold(.25); // Set the max age to 200. subnet_->setCacheMaxAge(200); IOAddress addr("192.0.2.105"); time_t now = time(NULL) - 100; // Allocated 100 seconds ago. Lease4Ptr lease(new Lease4(addr, hwaddr_, clientid_, valid, now, subnet_->getID())); ASSERT_FALSE(lease->expired()); ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); // Create a context for fake allocation with rev_dns_update changed. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, addr, false, true, "", true); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); lease = engine->allocateLease4(ctx); // Check that we got that single lease. ASSERT_TRUE(lease); EXPECT_EQ(addr, lease->addr_); // The lease was not reused. EXPECT_EQ(0, lease->reuseable_valid_lft_); } // This test checks if a lease can't be reused in DHCPREQUEST (real allocation) // when hostname changed. TEST_F(AllocEngine4Test, requestCacheHostname4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); // Set valid lifetime to 500. uint32_t valid = 500; subnet_->setValid(valid); // Set the max age to 200. subnet_->setCacheMaxAge(200); IOAddress addr("192.0.2.105"); time_t now = time(NULL) - 100; // Allocated 100 seconds ago. Lease4Ptr lease(new Lease4(addr, hwaddr_, clientid_, valid, now, subnet_->getID(), false, false, "foo")); ASSERT_FALSE(lease->expired()); // Create a context for real allocation with fwd_dns_update changed. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, addr, false, false, "bar", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); lease = engine->allocateLease4(ctx); // Check that we got that single lease. ASSERT_TRUE(lease); EXPECT_EQ(addr, lease->addr_); // 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. Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(addr); ASSERT_TRUE(from_mgr); detailCompareLease(lease, from_mgr); } // Verifies that AllocEngine::getValidLft(ctx4) returns the appropriate // lifetime value based on the context content. TEST_F(AllocEngine4Test, getValidLft4) { AllocEngine engine(0); // Let's make three classes, two with valid-lifetime and one without, // and add them to the dictionary. ClientClassDictionaryPtr dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary(); ClientClassDefPtr class_def(new ClientClassDef("valid_one", ExpressionPtr())); Triplet valid_one(50, 100, 150); class_def->setValid(valid_one); dictionary->addClass(class_def); class_def.reset(new ClientClassDef("valid_two", ExpressionPtr())); Tripletvalid_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(500, 1000, 1500)); // Describes a test scenario. struct Scenario { std::string desc_; // descriptive text for logging std::vector classes_; // class list of assigned classes uint32_t requested_lft_; // use as option 51 is > 0 uint32_t exp_valid_; // expected lifetime }; // Scenarios to test. std::vector scenarios = { { "BOOTP", { "BOOTP" }, 0, Lease::INFINITY_LFT }, { "no classes, no option", {}, 0, subnet_->getValid() }, { "no classes, option", {}, subnet_->getValid().getMin() + 50, subnet_->getValid().getMin() + 50 }, { "no classes, option too small", {}, subnet_->getValid().getMin() - 50, subnet_->getValid().getMin() }, { "no classes, option too big", {}, subnet_->getValid().getMax() + 50, subnet_->getValid().getMax() }, { "class unspecified, no option", { "valid_unspec" }, 0, subnet_->getValid() }, { "from last class, no option", { "valid_unspec", "valid_one" }, 0, valid_one.get() }, { "from first class, no option", { "valid_two", "valid_one" }, 0, valid_two.get() }, { "class plus option", { "valid_one" }, valid_one.getMin() + 25, valid_one.getMin() + 25 }, { "class plus option too small", { "valid_one" }, valid_one.getMin() - 25, valid_one.getMin() }, { "class plus option 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::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_, IOAddress("0.0.0.0"), false, false, "", false); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); // Add client classes (if any) for (auto class_name : scenario.classes_) { ctx.query_->addClass(class_name); } // Add client option (if one) if (scenario.requested_lft_) { OptionUint32Ptr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME, scenario.requested_lft_)); ctx.query_->addOption(opt); } Lease4Ptr lease = engine.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ(lease->valid_lft_, scenario.exp_valid_); } } } // Verifies that AllocEngine::getValidLft(ctx4) returns the appropriate // lifetime value based on the context content. TEST_F(AllocEngine4Test, getTemplateClassValidLft4) { AllocEngine engine(0); // Let's make three classes, two with valid-lifetime and one without, // and add them to the dictionary. ClientClassDictionaryPtr dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary(); ExpressionPtr match_expr; ExpressionParser parser; ElementPtr test_cfg = Element::create("'valid_one_value'"); parser.parse(match_expr, test_cfg, AF_INET, EvalContext::acceptAll, EvalContext::PARSER_STRING); ClientClassDefPtr class_def(new TemplateClientClassDef("valid_one", match_expr)); Triplet valid_one(50, 100, 150); class_def->setValid(valid_one); dictionary->addClass(class_def); test_cfg = Element::create("'valid_two_value'"); parser.parse(match_expr, test_cfg, AF_INET, EvalContext::acceptAll, EvalContext::PARSER_STRING); class_def.reset(new TemplateClientClassDef("valid_two", match_expr)); Tripletvalid_two(200, 250, 300); class_def->setValid(valid_two); dictionary->addClass(class_def); test_cfg = Element::create("'valid_unspec_value'"); parser.parse(match_expr, test_cfg, AF_INET, EvalContext::acceptAll, EvalContext::PARSER_STRING); class_def.reset(new TemplateClientClassDef("valid_unspec", match_expr)); dictionary->addClass(class_def); // Commit our class changes. CfgMgr::instance().commit(); // Update the subnet's triplet to something more useful. subnet_->setValid(Triplet(500, 1000, 1500)); // Describes a test scenario. struct Scenario { std::string desc_; // descriptive text for logging std::vector classes_; // class list of assigned classes uint32_t requested_lft_; // use as option 51 is > 0 uint32_t exp_valid_; // expected lifetime }; // Scenarios to test. std::vector scenarios = { { "BOOTP", { "BOOTP" }, 0, Lease::INFINITY_LFT }, { "no classes, no option", {}, 0, subnet_->getValid() }, { "no classes, option", {}, subnet_->getValid().getMin() + 50, subnet_->getValid().getMin() + 50 }, { "no classes, option too small", {}, subnet_->getValid().getMin() - 50, subnet_->getValid().getMin() }, { "no classes, option too big", {}, subnet_->getValid().getMax() + 50, subnet_->getValid().getMax() }, { "class unspecified, no option", { "valid_unspec" }, 0, subnet_->getValid() }, { "from last class, no option", { "valid_unspec", "valid_one" }, 0, valid_one.get() }, { "from first class, no option", { "valid_two", "valid_one" }, 0, valid_two.get() }, { "class plus option", { "valid_one" }, valid_one.getMin() + 25, valid_one.getMin() + 25 }, { "class plus option too small", { "valid_one" }, valid_one.getMin() - 25, valid_one.getMin() }, { "class plus option 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::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_, IOAddress("0.0.0.0"), false, false, "", false); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); // Add client classes (if any) for (auto class_name : scenario.classes_) { if (class_name == "BOOTP") { ctx.query_->addClass(class_name); } else { string subclass(TemplateClientClassDef::SPAWN_CLASS_PREFIX); subclass += class_name; subclass += "_value"; ctx.query_->addSubClass(class_name, subclass); } } // Add client option (if one) if (scenario.requested_lft_) { OptionUint32Ptr opt(new OptionUint32(Option::V4, DHO_DHCP_LEASE_TIME, scenario.requested_lft_)); ctx.query_->addOption(opt); } Lease4Ptr lease = engine.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ(lease->valid_lft_, scenario.exp_valid_); } } } // This test checks that deleteRelease handles BOOTP leases. TEST_F(AllocEngine4Test, bootpDelete) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, true, "somehost.example.com.", false); subnet_->setValid(Triplet(1, 3, 5)); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); // Make the query a BOOTP one. ctx.query_->addClass("BOOTP"); Lease4Ptr lease = engine->allocateLease4(ctx); // The new lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // Check that we got a lease ASSERT_TRUE(lease); // Check that is belongs to the right subnet and client. EXPECT_EQ(lease->subnet_id_, subnet_->getID()); EXPECT_TRUE(subnet_->inRange(lease->addr_)); EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease->addr_)); ASSERT_TRUE(lease->client_id_); EXPECT_TRUE(*lease->client_id_ == *clientid_); ASSERT_TRUE(lease->hwaddr_); EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_); // Check the valid lifetime is infinite. uint32_t infinity_lft = Lease::INFINITY_LFT; EXPECT_EQ(infinity_lft, lease->valid_lft_); // Check that the lease is indeed in LeaseMgr Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); // Now delete it. bool deleted = false; ASSERT_NO_THROW(deleted = LeaseMgrFactory::instance().deleteLease(lease)); EXPECT_TRUE(deleted); from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); EXPECT_FALSE(from_mgr); } // This test verifies that all addresses in a pool can be allocated. TEST_F(AllocEngine4Test, fullPool) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); // Get rid of the default subnet configuration. CfgMgr& cfg_mgr = CfgMgr::instance(); cfg_mgr.clear(); // Configure a larger subnet with a /24 pool. subnet_ = Subnet4::create(IOAddress("10.0.0.0"), 8, 1, 2, 3, SubnetID(10)); pool_ = Pool4Ptr(new Pool4(IOAddress("10.0.1.0"), IOAddress("10.0.1.255"))); subnet_->addPool(pool_); cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_); // Set exclude-first-last to false. cfg_mgr.getStagingCfg()->setExcludeFirstLast24(false); cfg_mgr.commit(); size_t cnt = 0; vector found(256, false); while (true) { vector duid = clientid_->getClientId(); duid[6] = cnt >> 8; duid[7] = cnt & 0xff; ClientIdPtr clientid(new ClientId(duid)); HWAddrPtr hwaddr(new HWAddr(*hwaddr_)); hwaddr->hwaddr_[4] = cnt >> 8; hwaddr->hwaddr_[5] = cnt & 0xff; AllocEngine::ClientContext4 ctx(subnet_, clientid, hwaddr, IOAddress("0.0.0.0"), false, false, "foo.bar", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234 + cnt)); Lease4Ptr lease = engine->allocateLease4(ctx); if (!lease) { break; } const vector& addr = lease->addr_.toBytes(); ASSERT_EQ(4, addr.size()); EXPECT_EQ(10, addr[0]); EXPECT_EQ(0, addr[1]); EXPECT_EQ(1, addr[2]); EXPECT_FALSE(found[addr[3]]); found[addr[3]] = true; ++cnt; // Catch unbound loop. ASSERT_LT(cnt, 1000); } EXPECT_EQ(256, cnt); EXPECT_EQ(found, vector(256, true)); } // This test verifies that all addresses in a subnet can be allocated. TEST_F(AllocEngine4Test, fullSubnet24) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); // Get rid of the default subnet configuration. CfgMgr& cfg_mgr = CfgMgr::instance(); cfg_mgr.clear(); // Configure a larger subnet with a /24 pool. subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10)); pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.0"), IOAddress("192.0.2.255"))); subnet_->addPool(pool_); cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_); // Set exclude-first-last to false. cfg_mgr.getStagingCfg()->setExcludeFirstLast24(false); cfg_mgr.commit(); size_t cnt = 0; vector found(256, false); while (true) { vector duid = clientid_->getClientId(); duid[6] = cnt >> 8; duid[7] = cnt & 0xff; ClientIdPtr clientid(new ClientId(duid)); HWAddrPtr hwaddr(new HWAddr(*hwaddr_)); hwaddr->hwaddr_[4] = cnt >> 8; hwaddr->hwaddr_[5] = cnt & 0xff; AllocEngine::ClientContext4 ctx(subnet_, clientid, hwaddr, IOAddress("0.0.0.0"), false, false, "foo.bar", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234 + cnt)); Lease4Ptr lease = engine->allocateLease4(ctx); if (!lease) { break; } const vector& addr = lease->addr_.toBytes(); ASSERT_EQ(4, addr.size()); EXPECT_EQ(192, addr[0]); EXPECT_EQ(0, addr[1]); EXPECT_EQ(2, addr[2]); EXPECT_FALSE(found[addr[3]]); found[addr[3]] = true; ++cnt; // Catch unbound loop. ASSERT_LT(cnt, 1000); } EXPECT_EQ(256, cnt); EXPECT_EQ(found, vector(256, true)); } // This test verifies that not all addresses in a pool can be allocated when // exclude-first-last is true. TEST_F(AllocEngine4Test, excludeFirstLast) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); // Get rid of the default subnet configuration. CfgMgr& cfg_mgr = CfgMgr::instance(); cfg_mgr.clear(); // Configure a larger subnet with a /24 pool. subnet_ = Subnet4::create(IOAddress("10.0.0.0"), 8, 1, 2, 3, SubnetID(10)); pool_ = Pool4Ptr(new Pool4(IOAddress("10.0.1.0"), IOAddress("10.0.1.255"))); subnet_->addPool(pool_); cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_); // Set exclude-first-last to true. cfg_mgr.getStagingCfg()->setExcludeFirstLast24(true); cfg_mgr.commit(); size_t cnt = 0; vector found(256, false); while (true) { vector duid = clientid_->getClientId(); duid[6] = cnt >> 8; duid[7] = cnt & 0xff; ClientIdPtr clientid(new ClientId(duid)); HWAddrPtr hwaddr(new HWAddr(*hwaddr_)); hwaddr->hwaddr_[4] = cnt >> 8; hwaddr->hwaddr_[5] = cnt & 0xff; AllocEngine::ClientContext4 ctx(subnet_, clientid, hwaddr, IOAddress("0.0.0.0"), false, false, "foo.bar", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234 + cnt)); Lease4Ptr lease = engine->allocateLease4(ctx); if (!lease) { break; } const vector& addr = lease->addr_.toBytes(); ASSERT_EQ(4, addr.size()); EXPECT_EQ(10, addr[0]); EXPECT_EQ(0, addr[1]); EXPECT_EQ(1, addr[2]); EXPECT_FALSE(found[addr[3]]); found[addr[3]] = true; ++cnt; // Catch unbound loop. ASSERT_LT(cnt, 1000); } EXPECT_EQ(254, cnt); vector expected(256, true); expected[0] = false; expected[255] = false; EXPECT_EQ(expected, found); } // This test verifies that not all addresses in a subnet can be allocated when // exclude-first-last is true. TEST_F(AllocEngine4Test, excludeFirstLast24) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); // Get rid of the default subnet configuration. CfgMgr& cfg_mgr = CfgMgr::instance(); cfg_mgr.clear(); // Configure a larger subnet with a /24 pool. subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10)); pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.0"), IOAddress("192.0.2.255"))); subnet_->addPool(pool_); cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_); // Set exclude-first-last to true. cfg_mgr.getStagingCfg()->setExcludeFirstLast24(true); cfg_mgr.commit(); size_t cnt = 0; vector found(256, false); while (true) { vector duid = clientid_->getClientId(); duid[6] = cnt >> 8; duid[7] = cnt & 0xff; ClientIdPtr clientid(new ClientId(duid)); HWAddrPtr hwaddr(new HWAddr(*hwaddr_)); hwaddr->hwaddr_[4] = cnt >> 8; hwaddr->hwaddr_[5] = cnt & 0xff; AllocEngine::ClientContext4 ctx(subnet_, clientid, hwaddr, IOAddress("0.0.0.0"), false, false, "foo.bar", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234 + cnt)); Lease4Ptr lease = engine->allocateLease4(ctx); if (!lease) { break; } const vector& addr = lease->addr_.toBytes(); ASSERT_EQ(4, addr.size()); EXPECT_EQ(192, addr[0]); EXPECT_EQ(0, addr[1]); EXPECT_EQ(2, addr[2]); EXPECT_FALSE(found[addr[3]]); found[addr[3]] = true; ++cnt; // Catch unbound loop. ASSERT_LT(cnt, 1000); } EXPECT_EQ(254, cnt); vector expected(256, true); expected[0] = false; expected[255] = false; EXPECT_EQ(expected, found); } // This test verifies that all addresses in a subnet can be allocated when // exclude-first-last is true but the prefix length is greater than 24. TEST_F(AllocEngine4Test, excludeFirst25) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); // Get rid of the default subnet configuration. CfgMgr& cfg_mgr = CfgMgr::instance(); cfg_mgr.clear(); // Configure a smaller subnet with a /25 pool. subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 25, 1, 2, 3, SubnetID(10)); pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.0"), IOAddress("192.0.2.127"))); subnet_->addPool(pool_); cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_); // Set exclude-first-last to true. cfg_mgr.getStagingCfg()->setExcludeFirstLast24(true); cfg_mgr.commit(); size_t cnt = 0; vector found(128, false); while (true) { vector duid = clientid_->getClientId(); duid[6] = cnt >> 8; duid[7] = cnt & 0xff; ClientIdPtr clientid(new ClientId(duid)); HWAddrPtr hwaddr(new HWAddr(*hwaddr_)); hwaddr->hwaddr_[4] = cnt >> 8; hwaddr->hwaddr_[5] = cnt & 0xff; AllocEngine::ClientContext4 ctx(subnet_, clientid, hwaddr, IOAddress("0.0.0.0"), false, false, "foo.bar", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234 + cnt)); Lease4Ptr lease = engine->allocateLease4(ctx); if (!lease) { break; } const vector& addr = lease->addr_.toBytes(); ASSERT_EQ(4, addr.size()); EXPECT_EQ(192, addr[0]); EXPECT_EQ(0, addr[1]); EXPECT_EQ(2, addr[2]); ASSERT_GT(128, addr[3]); EXPECT_FALSE(found[addr[3]]); found[addr[3]] = true; ++cnt; // Catch unbound loop. ASSERT_LT(cnt, 1000); } EXPECT_EQ(128, cnt); EXPECT_EQ(found, vector(128, true)); } // This test verifies that all addresses in a subnet can be allocated when // exclude-first-last is true but the prefix length is greater than 24. TEST_F(AllocEngine4Test, excludeLast25) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); // Get rid of the default subnet configuration. CfgMgr& cfg_mgr = CfgMgr::instance(); cfg_mgr.clear(); // Configure a smaller subnet with a /25 pool. subnet_ = Subnet4::create(IOAddress("192.0.2.128"), 25, 1, 2, 3, SubnetID(10)); pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.128"), IOAddress("192.0.2.255"))); subnet_->addPool(pool_); cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_); // Set exclude-first-last to true. cfg_mgr.getStagingCfg()->setExcludeFirstLast24(true); cfg_mgr.commit(); size_t cnt = 0; vector found(128, false); while (true) { vector duid = clientid_->getClientId(); duid[6] = cnt >> 8; duid[7] = cnt & 0xff; ClientIdPtr clientid(new ClientId(duid)); HWAddrPtr hwaddr(new HWAddr(*hwaddr_)); hwaddr->hwaddr_[4] = cnt >> 8; hwaddr->hwaddr_[5] = cnt & 0xff; AllocEngine::ClientContext4 ctx(subnet_, clientid, hwaddr, IOAddress("0.0.0.0"), false, false, "foo.bar", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234 + cnt)); Lease4Ptr lease = engine->allocateLease4(ctx); if (!lease) { break; } const vector& addr = lease->addr_.toBytes(); ASSERT_EQ(4, addr.size()); EXPECT_EQ(192, addr[0]); EXPECT_EQ(0, addr[1]); EXPECT_EQ(2, addr[2]); ASSERT_LE(128, addr[3]); EXPECT_FALSE(found[addr[3] - 128]); found[addr[3] - 128] = true; ++cnt; // Catch unbound loop. ASSERT_LT(cnt, 1000); } EXPECT_EQ(128, cnt); EXPECT_EQ(found, vector(128, true)); } // This test verifies that an excluded address can be allocated when requested // and exclude-first-last is true. TEST_F(AllocEngine4Test, excludeFirstLastRequested) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); // Get rid of the default subnet configuration. CfgMgr& cfg_mgr = CfgMgr::instance(); cfg_mgr.clear(); // Configure a larger subnet with a /24 pool. subnet_ = Subnet4::create(IOAddress("10.0.0.0"), 8, 1, 2, 3, SubnetID(10)); pool_ = Pool4Ptr(new Pool4(IOAddress("10.0.1.0"), IOAddress("10.0.1.255"))); subnet_->addPool(pool_); cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_); // Set exclude-first-last to true. cfg_mgr.getStagingCfg()->setExcludeFirstLast24(true); cfg_mgr.commit(); // Request the first address. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("10.0.1.0"), false, false, "foo.bar", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); Lease4Ptr lease = engine->allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("10.0.1.0", lease->addr_.toText()); // Request the last address. AllocEngine::ClientContext4 ctx2(subnet_, clientid2_, hwaddr2_, IOAddress("10.0.1.255"), false, false, "bar.foo", false); ctx2.query_.reset(new Pkt4(DHCPREQUEST, 2345)); lease = engine->allocateLease4(ctx2); ASSERT_TRUE(lease); EXPECT_EQ("10.0.1.255", lease->addr_.toText()); } // This test verifies that an excluded address can be allocated when reserved // and exclude-first-last is true. TEST_F(AllocEngine4Test, excludeFirstLastReserver) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); // Get rid of the default subnet configuration. CfgMgr& cfg_mgr = CfgMgr::instance(); cfg_mgr.clear(); // Configure a larger subnet with a /24 pool. subnet_ = Subnet4::create(IOAddress("10.0.0.0"), 8, 1, 2, 3, SubnetID(10)); pool_ = Pool4Ptr(new Pool4(IOAddress("10.0.1.0"), IOAddress("10.0.1.255"))); subnet_->addPool(pool_); cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_); // Set exclude-first-last to true. cfg_mgr.getStagingCfg()->setExcludeFirstLast24(true); // Add reservations. HostPtr host(new Host(&hwaddr_->hwaddr_[0], hwaddr_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("10.0.1.0"))); cfg_mgr.getStagingCfg()->getCfgHosts()->add(host); HostPtr host2(new Host(&hwaddr2_->hwaddr_[0], hwaddr2_->hwaddr_.size(), Host::IDENT_HWADDR, subnet_->getID(), SUBNET_ID_UNUSED, IOAddress("10.0.1.255"))); cfg_mgr.getStagingCfg()->getCfgHosts()->add(host2); subnet_->setReservationsInSubnet(true); subnet_->setReservationsOutOfPool(false); cfg_mgr.commit(); // Request the first address. AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "foo.bar", false); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); AllocEngine::findReservation(ctx); Lease4Ptr lease = engine->allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ("10.0.1.0", lease->addr_.toText()); // Request the last address. AllocEngine::ClientContext4 ctx2(subnet_, clientid2_, hwaddr2_, IOAddress("0.0.0.0"), false, false, "bar.foo", false); ctx2.query_.reset(new Pkt4(DHCPREQUEST, 2345)); AllocEngine::findReservation(ctx2); lease = engine->allocateLease4(ctx2); ASSERT_TRUE(lease); EXPECT_EQ("10.0.1.255", lease->addr_.toText()); } #ifdef HAVE_MYSQL /// @brief Extension of the fixture class to use the MySQL backend. class MySqlAllocEngine4Test : public AllocEngine4Test { public: /// @brief Constructor. MySqlAllocEngine4Test() { // Ensure we have the proper schema with no transient data. db::test::createMySQLSchema(); factory_.create(db::test::validMySQLConnectionString()); } /// @brief Destructor. ~MySqlAllocEngine4Test() { // If data wipe enabled, delete transient data otherwise destroy // the schema. db::test::destroyMySQLSchema(); LeaseMgrFactory::destroy(); } }; // This test checks that simple allocation handles BOOTP queries. TEST_F(MySqlAllocEngine4Test, bootpAlloc4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, true, "somehost.example.com.", false); subnet_->setValid(Triplet(1, 3, 5)); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); // Make the query a BOOTP one. ctx.query_->addClass("BOOTP"); Lease4Ptr lease = engine->allocateLease4(ctx); // The new lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // Check that we got a lease ASSERT_TRUE(lease); // Check that is belongs to the right subnet and client. EXPECT_EQ(lease->subnet_id_, subnet_->getID()); EXPECT_TRUE(subnet_->inRange(lease->addr_)); EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease->addr_)); ASSERT_TRUE(lease->client_id_); EXPECT_TRUE(*lease->client_id_ == *clientid_); ASSERT_TRUE(lease->hwaddr_); EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_); // Check the valid lifetime is infinite. uint32_t infinity_lft = Lease::INFINITY_LFT; EXPECT_EQ(infinity_lft, lease->valid_lft_); // Check that the lease is indeed in LeaseMgr Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); // The MySQL database does not keep the hwtype for DHCPv4 leases. from_mgr->hwaddr_->htype_ = HTYPE_FDDI; // Now check that the lease in LeaseMgr has the same parameters detailCompareLease(lease, from_mgr); } // This test checks simple renewal handles BOOTP queries. TEST_F(MySqlAllocEngine4Test, bootpRenew4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, true, "somehost.example.com.", false); subnet_->setValid(Triplet(1, 3, 5)); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); // Make the query a BOOTP one. ctx.query_->addClass("BOOTP"); Lease4Ptr lease = engine->allocateLease4(ctx); // Check that we got a lease. ASSERT_TRUE(lease); // Check that is belongs to the right subnet and client. EXPECT_EQ(lease->subnet_id_, subnet_->getID()); EXPECT_TRUE(subnet_->inRange(lease->addr_)); EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease->addr_)); ASSERT_TRUE(lease->client_id_); EXPECT_TRUE(*lease->client_id_ == *clientid_); ASSERT_TRUE(lease->hwaddr_); EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_); // Check the valid lifetime is infinite. uint32_t infinity_lft = Lease::INFINITY_LFT; EXPECT_EQ(infinity_lft, lease->valid_lft_); // The new lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // Do it again, this should amount to the renew of an existing lease Lease4Ptr lease2 = engine->allocateLease4(ctx); // Check that we got a lease. ASSERT_TRUE(lease2); // Check that is belongs to the right subnet and client. EXPECT_EQ(lease2->subnet_id_, subnet_->getID()); EXPECT_TRUE(subnet_->inRange(lease2->addr_)); EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease2->addr_)); ASSERT_TRUE(lease2->client_id_); EXPECT_TRUE(*lease2->client_id_ == *clientid_); ASSERT_TRUE(lease2->hwaddr_); EXPECT_TRUE(*lease2->hwaddr_ == *hwaddr_); // Lease already existed, so old_lease should be set. EXPECT_TRUE(ctx.old_lease_); // Check the renewed valid lifetime has the max value. EXPECT_EQ(infinity_lft, lease2->valid_lft_); } // This test checks that deleteRelease handles BOOTP leases. TEST_F(MySqlAllocEngine4Test, bootpDelete) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, true, "somehost.example.com.", false); subnet_->setValid(Triplet(1, 3, 5)); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); // Make the query a BOOTP one. ctx.query_->addClass("BOOTP"); Lease4Ptr lease = engine->allocateLease4(ctx); // The new lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // Check that we got a lease ASSERT_TRUE(lease); // Check that is belongs to the right subnet and client. EXPECT_EQ(lease->subnet_id_, subnet_->getID()); EXPECT_TRUE(subnet_->inRange(lease->addr_)); EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease->addr_)); ASSERT_TRUE(lease->client_id_); EXPECT_TRUE(*lease->client_id_ == *clientid_); ASSERT_TRUE(lease->hwaddr_); EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_); // Check the valid lifetime is infinite. uint32_t infinity_lft = Lease::INFINITY_LFT; EXPECT_EQ(infinity_lft, lease->valid_lft_); // Check that the lease is indeed in LeaseMgr Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); // Now delete it. bool deleted = false; ASSERT_NO_THROW(deleted = LeaseMgrFactory::instance().deleteLease(lease)); EXPECT_TRUE(deleted); from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); EXPECT_FALSE(from_mgr); } #endif #ifdef HAVE_PGSQL /// @brief Extension of the fixture class to use the PostgreSql backend. class PgSqlAllocEngine4Test : public AllocEngine4Test { public: /// @brief Constructor. PgSqlAllocEngine4Test() { // Ensure we have the proper schema with no transient data. db::test::createPgSQLSchema(); factory_.create(db::test::validPgSQLConnectionString()); } /// @brief Destructor. ~PgSqlAllocEngine4Test() { // If data wipe enabled, delete transient data otherwise destroy // the schema. db::test::destroyPgSQLSchema(); LeaseMgrFactory::destroy(); } }; // This test checks that simple allocation handles BOOTP queries. TEST_F(PgSqlAllocEngine4Test, bootpAlloc4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, true, "somehost.example.com.", false); subnet_->setValid(Triplet(1, 3, 5)); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); // Make the query a BOOTP one. ctx.query_->addClass("BOOTP"); Lease4Ptr lease = engine->allocateLease4(ctx); // The new lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // Check that we got a lease ASSERT_TRUE(lease); // Check that is belongs to the right subnet and client. EXPECT_EQ(lease->subnet_id_, subnet_->getID()); EXPECT_TRUE(subnet_->inRange(lease->addr_)); EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease->addr_)); ASSERT_TRUE(lease->client_id_); EXPECT_TRUE(*lease->client_id_ == *clientid_); ASSERT_TRUE(lease->hwaddr_); EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_); // Check the valid lifetime is infinite. uint32_t infinity_lft = Lease::INFINITY_LFT; EXPECT_EQ(infinity_lft, lease->valid_lft_); // Check that the lease is indeed in LeaseMgr Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); // The PostgreSql database does not keep the hwtype for DHCPv4 leases. from_mgr->hwaddr_->htype_ = HTYPE_FDDI; // Now check that the lease in LeaseMgr has the same parameters detailCompareLease(lease, from_mgr); } // This test checks simple renewal handles BOOTP queries. TEST_F(PgSqlAllocEngine4Test, bootpRenew4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, true, "somehost.example.com.", false); subnet_->setValid(Triplet(1, 3, 5)); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); // Make the query a BOOTP one. ctx.query_->addClass("BOOTP"); Lease4Ptr lease = engine->allocateLease4(ctx); // Check that we got a lease. ASSERT_TRUE(lease); // Check that is belongs to the right subnet and client. EXPECT_EQ(lease->subnet_id_, subnet_->getID()); EXPECT_TRUE(subnet_->inRange(lease->addr_)); EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease->addr_)); ASSERT_TRUE(lease->client_id_); EXPECT_TRUE(*lease->client_id_ == *clientid_); ASSERT_TRUE(lease->hwaddr_); EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_); // Check the valid lifetime is infinite. uint32_t infinity_lft = Lease::INFINITY_LFT; EXPECT_EQ(infinity_lft, lease->valid_lft_); // The new lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // Do it again, this should amount to the renew of an existing lease Lease4Ptr lease2 = engine->allocateLease4(ctx); // Check that we got a lease. ASSERT_TRUE(lease2); // Check that is belongs to the right subnet and client. EXPECT_EQ(lease2->subnet_id_, subnet_->getID()); EXPECT_TRUE(subnet_->inRange(lease2->addr_)); EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease2->addr_)); ASSERT_TRUE(lease2->client_id_); EXPECT_TRUE(*lease2->client_id_ == *clientid_); ASSERT_TRUE(lease2->hwaddr_); EXPECT_TRUE(*lease2->hwaddr_ == *hwaddr_); // Lease already existed, so old_lease should be set. EXPECT_TRUE(ctx.old_lease_); // Check the renewed valid lifetime has the max value. EXPECT_EQ(infinity_lft, lease2->valid_lft_); } // This test checks that deleteRelease handles BOOTP leases. TEST_F(PgSqlAllocEngine4Test, bootpDelete) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); AllocEngine::ClientContext4 ctx(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, true, "somehost.example.com.", false); subnet_->setValid(Triplet(1, 3, 5)); ctx.query_.reset(new Pkt4(DHCPREQUEST, 1234)); // Make the query a BOOTP one. ctx.query_->addClass("BOOTP"); Lease4Ptr lease = engine->allocateLease4(ctx); // The new lease has been allocated, so the old lease should not exist. ASSERT_FALSE(ctx.old_lease_); // Check that we got a lease ASSERT_TRUE(lease); // Check that is belongs to the right subnet and client. EXPECT_EQ(lease->subnet_id_, subnet_->getID()); EXPECT_TRUE(subnet_->inRange(lease->addr_)); EXPECT_TRUE(subnet_->inPool(Lease::TYPE_V4, lease->addr_)); ASSERT_TRUE(lease->client_id_); EXPECT_TRUE(*lease->client_id_ == *clientid_); ASSERT_TRUE(lease->hwaddr_); EXPECT_TRUE(*lease->hwaddr_ == *hwaddr_); // Check the valid lifetime is infinite. uint32_t infinity_lft = Lease::INFINITY_LFT; EXPECT_EQ(infinity_lft, lease->valid_lft_); // Check that the lease is indeed in LeaseMgr Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); // Now delete it. bool deleted = false; ASSERT_NO_THROW(deleted = LeaseMgrFactory::instance().deleteLease(lease)); EXPECT_TRUE(deleted); from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); EXPECT_FALSE(from_mgr); } #endif // Verifies that offer_lft is non-zero, that an offered lease is stored // in the lease database. TEST_F(AllocEngine4Test, discoverOfferLft) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); IOAddress addr("192.0.2.15"); CfgMgr& cfg_mgr = CfgMgr::instance(); // Get rid of the default test configuration. cfg_mgr.clear(); // Create configuration similar to other tests, but with a single address pool subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10)); pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address subnet_->addPool(pool_); // Set subnet's offer-lifetime to a non-zero, positive value. uint32_t offer_lft = (subnet_->getValid() / 3); subnet_->setOfferLft(offer_lft); ASSERT_EQ(offer_lft, subnet_->getOfferLft().get()); cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_); // Ask for any address AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), true, true, "one", true); ctx1.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); // Check that we got that single lease Lease4Ptr lease = engine->allocateLease4(ctx1); ASSERT_TRUE(lease); EXPECT_EQ(addr, lease->addr_); EXPECT_EQ(offer_lft, lease->valid_lft_); EXPECT_FALSE(lease->fqdn_fwd_); EXPECT_FALSE(lease->fqdn_rev_); // Check that the lease has been stored by LeaseMgr. Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); EXPECT_EQ(offer_lft, from_mgr->valid_lft_); EXPECT_FALSE(from_mgr->fqdn_fwd_); EXPECT_FALSE(from_mgr->fqdn_rev_); // Try to discover an address for a second client. uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe }; HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER)); // Ask for any address. AllocEngine::ClientContext4 ctx2(subnet_, ClientIdPtr(), hwaddr2, IOAddress("0.0.0.0"), true, true, "two", true); ctx2.query_.reset(new Pkt4(DHCPDISCOVER, 1235)); // Verify that we did not get a lease. lease = engine->allocateLease4(ctx2); ASSERT_FALSE(lease); // Original client now does a DHCPREQUEST. Make sure we get the // previously offered lease with the proper valid lifetime. AllocEngine::ClientContext4 ctx3(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), true, true, "one", false); ctx3.query_.reset(new Pkt4(DHCPREQUEST, 1236)); lease = engine->allocateLease4(ctx3); ASSERT_TRUE(lease); EXPECT_EQ(addr, lease->addr_); from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); EXPECT_EQ(subnet_->getValid(), from_mgr->valid_lft_); } // Verifies that when offer_lft is non-zero, that an existing lease is // whose remaining life is larger than offer_lft, is offered as is. TEST_F(AllocEngine4Test, discoverOfferLftUseExistingLease4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); IOAddress addr("192.0.2.15"); CfgMgr& cfg_mgr = CfgMgr::instance(); // Get rid of the default test configuration. cfg_mgr.clear(); // Create configuration similar to other tests, but with a single address pool subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 300, SubnetID(10)); pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address subnet_->addPool(pool_); // Set subnet's offer-lifetime to a non-zero, positive value. uint32_t offer_lft = 100; subnet_->setOfferLft(offer_lft); ASSERT_EQ(offer_lft, subnet_->getOfferLft().get()); cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_); time_t now = time(NULL) - 5; // Allocated 5 seconds ago Lease4Ptr lease(new Lease4(addr, hwaddr_, clientid_, 300, now, subnet_->getID(), true, true, "somehost")); // Copy the lease, so as it can be compared with the old lease returned // by the allocation engine. Lease4 original_lease(*lease); // Lease was assigned 5 seconds ago, its valid lifetime is 300, its // remaining lifetime is still larger than offer_lft. ASSERT_FALSE(lease->expired()); ASSERT_EQ(300, lease->valid_lft_); ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); // CASE 1: Asking for any address AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "", true); ctx1.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); lease = engine->allocateLease4(ctx1); // Check that we got that single lease ASSERT_TRUE(lease); EXPECT_EQ(addr, lease->addr_); EXPECT_EQ(300, lease->valid_lft_); // We are reusing the existing lease, the old instance should be // returned. The returned instance should be the same as the original // lease. ASSERT_TRUE(ctx1.old_lease_); EXPECT_TRUE(original_lease == *ctx1.old_lease_); // Check that the lease has not been modified by LeaseMgr. Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); EXPECT_TRUE(original_lease == *from_mgr); } // Verifies that when offer_lft is non-zero, that an expired lease can // be reclaimed an offered correctly. TEST_F(AllocEngine4Test, discoverOfferLftReuseExpiredLease4) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(0))); ASSERT_TRUE(engine); IOAddress addr("192.0.2.15"); CfgMgr& cfg_mgr = CfgMgr::instance(); // Get rid of the default test configuration. cfg_mgr.clear(); // Create configuration similar to other tests, but with a single address pool subnet_ = Subnet4::create(IOAddress("192.0.2.0"), 24, 1, 2, 3, SubnetID(10)); pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address subnet_->addPool(pool_); // Set subnet's offer-lifetime to a non-zero, positive value. uint32_t offer_lft = (subnet_->getValid() / 3); subnet_->setOfferLft(offer_lft); ASSERT_EQ(offer_lft, subnet_->getOfferLft().get()); cfg_mgr.getStagingCfg()->getCfgSubnets4()->add(subnet_); // Just a different hw/client-id for the second client uint8_t hwaddr2_data[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe }; HWAddrPtr hwaddr2(new HWAddr(hwaddr2_data, sizeof(hwaddr2_data), HTYPE_ETHER)); uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 }; time_t now = time(NULL) - 500; // Allocated 500 seconds ago Lease4Ptr lease(new Lease4(addr, hwaddr2, clientid2, sizeof(clientid2), 495, now, subnet_->getID())); // Copy the lease, so as it can be compared with the old lease returned // by the allocation engine. Lease4 original_lease(*lease); // Lease was assigned 500 seconds ago, but its valid lifetime is 495, so it // is expired already ASSERT_TRUE(lease->expired()); ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); // CASE 1: Asking for any address AllocEngine::ClientContext4 ctx1(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), false, false, "", true); ctx1.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); lease = engine->allocateLease4(ctx1); // Check that we got that single lease ASSERT_TRUE(lease); EXPECT_EQ(addr, lease->addr_); EXPECT_EQ(offer_lft, lease->valid_lft_); EXPECT_FALSE(lease->fqdn_fwd_); EXPECT_FALSE(lease->fqdn_rev_); // We are reusing expired lease, the old (expired) instance should be // returned. The returned instance should be the same as the original // lease. ASSERT_TRUE(ctx1.old_lease_); EXPECT_TRUE(original_lease == *ctx1.old_lease_); // Check that the lease has been stored by LeaseMgr. Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); EXPECT_EQ(offer_lft, from_mgr->valid_lft_); EXPECT_FALSE(from_mgr->fqdn_fwd_); EXPECT_FALSE(from_mgr->fqdn_rev_); // Client now does a DHCPREQUEST. Make sure we get the // previously offered lease with the proper valid lifetime. AllocEngine::ClientContext4 ctx2(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), true, true, "one", false); ctx2.query_.reset(new Pkt4(DHCPREQUEST, 1235)); lease = engine->allocateLease4(ctx2); ASSERT_TRUE(lease); EXPECT_EQ(addr, lease->addr_); from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); ASSERT_TRUE(from_mgr); EXPECT_EQ(subnet_->getValid(), from_mgr->valid_lft_); } // Verifies that AllocEngine::getOfferLft(ctx4) returns the appropriate // lifetime value based on the context content. TEST_F(AllocEngine4Test, getOfferLft4) { AllocEngine engine(0); // Let's make three classes, two with offer-lifetime and one without, // and add them to the dictionary. ClientClassDictionaryPtr dictionary = CfgMgr::instance().getStagingCfg()->getClientClassDictionary(); ClientClassDefPtr class_def(new ClientClassDef("offer_lft_one", ExpressionPtr())); Optional offer_lft_one(100); class_def->setOfferLft(offer_lft_one); dictionary->addClass(class_def); class_def.reset(new ClientClassDef("offer_lft_two", ExpressionPtr())); Optionaloffer_lft_two(200); class_def->setOfferLft(offer_lft_two); dictionary->addClass(class_def); class_def.reset(new ClientClassDef("offer_lft_zero", ExpressionPtr())); Optionaloffer_lft_zero(0); class_def->setOfferLft(offer_lft_zero); dictionary->addClass(class_def); class_def.reset(new ClientClassDef("offer_lft_unspec", ExpressionPtr())); dictionary->addClass(class_def); // Commit our class changes. CfgMgr::instance().commit(); // Update the subnet's triplet to something more useful. subnet_->setOfferLft(Optional(300)); // Describes a test scenario. struct Scenario { std::string desc_; // descriptive text for logging std::vector classes_; // class list of assigned classes uint32_t exp_valid_lft_; // expected lease lifetime bool exp_allocate_; // true if lease should be allocated }; bool exp_allocate = true; // Scenarios to test. std::vector scenarios = { { "BOOTP", { "BOOTP" }, Lease::INFINITY_LFT, !exp_allocate, }, { "no classes", {}, subnet_->getOfferLft(), exp_allocate }, { "class unspecified", { "offer_lft_unspec" }, subnet_->getOfferLft(), exp_allocate }, { "from last class", { "offer_lft_unspec", "offer_lft_one" }, offer_lft_one.get(), exp_allocate }, { "from first class", { "offer_lft_two", "offer_lft_one" }, offer_lft_two.get(), exp_allocate }, { // Using class value of zero should override non-zero set at // subnet level, lease should have actual valid lft "zero from class", { "offer_lft_zero" }, subnet_->getValid(), !exp_allocate } }; // Iterate over the scenarios and verify the correct outcome. for (auto scenario : scenarios) { SCOPED_TRACE(scenario.desc_); { // Create a context; AllocEngine::ClientContext4 ctx(subnet_, ClientIdPtr(), hwaddr_, IOAddress("0.0.0.0"), false, false, "", true); ctx.query_.reset(new Pkt4(DHCPDISCOVER, 1234)); // Add client classes (if any) for (auto class_name : scenario.classes_) { ctx.query_->addClass(class_name); } Lease4Ptr lease = engine.allocateLease4(ctx); ASSERT_TRUE(lease); EXPECT_EQ(lease->valid_lft_, scenario.exp_valid_lft_); Lease4Ptr from_mgr = LeaseMgrFactory::instance().getLease4(lease->addr_); if (!scenario.exp_allocate_) { ASSERT_FALSE(from_mgr); } else { ASSERT_TRUE(from_mgr); EXPECT_EQ(from_mgr->valid_lft_, scenario.exp_valid_lft_); ASSERT_TRUE(LeaseMgrFactory::instance().deleteLease(from_mgr)); } } } } } // namespace test } // namespace dhcp } // namespace isc