From 32e13fbffae1a721a44f8b764ecb5ee066e64bf5 Mon Sep 17 00:00:00 2001 From: Tomek Mrugalski Date: Fri, 28 Dec 2012 17:43:13 +0100 Subject: [PATCH] [2320] Allocation Engine for IPv4 implemented --- src/lib/dhcpsrv/alloc_engine.cc | 189 ++++++++ src/lib/dhcpsrv/alloc_engine.h | 15 +- src/lib/dhcpsrv/lease_mgr.h | 7 +- src/lib/dhcpsrv/memfile_lease_mgr.cc | 61 ++- src/lib/dhcpsrv/memfile_lease_mgr.h | 16 + src/lib/dhcpsrv/mysql_lease_mgr.cc | 3 +- .../dhcpsrv/tests/alloc_engine_unittest.cc | 458 +++++++++++++++++- src/lib/dhcpsrv/tests/cfgmgr_unittest.cc | 5 + src/lib/dhcpsrv/tests/lease_mgr_unittest.cc | 6 +- tests/tools/perfdhcp/test_control.cc | 10 +- 10 files changed, 724 insertions(+), 46 deletions(-) diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index 60e3c2386b..dfe283ed69 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@ -260,6 +260,114 @@ AllocEngine::allocateAddress6(const Subnet6Ptr& subnet, << " tries"); } +Lease4Ptr +AllocEngine::allocateAddress4(const SubnetPtr& subnet, + const ClientIdPtr& clientid, + const HWAddrPtr& hwaddr, + const IOAddress& hint, + bool fake_allocation /* = false */ ) { + + // That check is not necessary. We create allocator in AllocEngine + // constructor + if (!allocator_) { + isc_throw(InvalidOperation, "No allocator selected"); + } + + // check if there's existing lease for that subnet/clientid/hwaddr combination. + Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(hwaddr->hwaddr_, subnet->getID()); + if (existing) { + // we have a lease already. This is a returning client, probably after + // his reboot. + return (existing); + } + + existing = LeaseMgrFactory::instance().getLease4(*clientid, subnet->getID()); + if (existing) { + // we have a lease already. This is a returning client, probably after + // his reboot. + + // @todo: produce a warning. We haven't found him using MAC address, but + // we found him using client-id + return (existing); + } + + // check if the hint is in pool and is available + if (subnet->inPool(hint)) { + existing = LeaseMgrFactory::instance().getLease4(hint); + if (!existing) { + /// @todo: check if the hint is reserved once we have host support + /// implemented + + // the hint is valid and not currently used, let's create a lease for it + Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, hint, fake_allocation); + + // It can happen that the lease allocation failed (we could have lost + // the race condition. That means that the hint is lo longer usable and + // we need to continue the regular allocation path. + if (lease) { + return (lease); + } + } else { + if (existing->expired()) { + return (reuseExpiredLease(existing, subnet, clientid, hwaddr, + fake_allocation)); + } + + } + } + + // Hint is in the pool but is not available. Search the pool until first of + // the following occurs: + // - we find a free address + // - we find an address for which the lease has expired + // - we exhaust number of tries + // + // @todo: Current code does not handle pool exhaustion well. It will be + // improved. Current problems: + // 1. with attempts set to too large value (e.g. 1000) and a small pool (e.g. + // 10 addresses), we will iterate over it 100 times before giving up + // 2. attempts 0 mean unlimited (this is really UINT_MAX, not infinite) + // 3. the whole concept of infinite attempts is just asking for infinite loop + // We may consider some form or reference counting (this pool has X addresses + // left), but this has one major problem. We exactly control allocation + // moment, but we currently do not control expiration time at all + + unsigned int i = attempts_; + do { + IOAddress candidate = allocator_->pickAddress(subnet, clientid, hint); + + /// @todo: check if the address is reserved once we have host support + /// implemented + + Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(candidate); + if (!existing) { + // there's no existing lease for selected candidate, so it is + // free. Let's allocate it. + Lease4Ptr lease = createLease4(subnet, clientid, hwaddr, candidate, + fake_allocation); + if (lease) { + return (lease); + } + + // Although the address was free just microseconds ago, it may have + // been taken just now. If the lease insertion fails, we continue + // allocation attempts. + } else { + if (existing->expired()) { + return (reuseExpiredLease(existing, subnet, clientid, hwaddr, + fake_allocation)); + } + } + + // continue trying allocation until we run out of attempts + // (or attempts are set to 0, which means infinite) + --i; + } while ( i || !attempts_); + + isc_throw(AllocFailed, "Failed to allocate address after " << attempts_ + << " tries"); +} + Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired, const Subnet6Ptr& subnet, const DuidPtr& duid, @@ -300,6 +408,45 @@ Lease6Ptr AllocEngine::reuseExpiredLease(Lease6Ptr& expired, return (expired); } +Lease4Ptr AllocEngine::reuseExpiredLease(Lease4Ptr& expired, + const SubnetPtr& subnet, + const ClientIdPtr& clientid, + const HWAddrPtr& hwaddr, + bool fake_allocation /*= false */ ) { + + if (!expired->expired()) { + isc_throw(BadValue, "Attempt to recycle lease that is still valid"); + } + + // address, lease type and prefixlen (0) stay the same + expired->client_id_ = clientid; + expired->hwaddr_ = hwaddr->hwaddr_; + expired->valid_lft_ = subnet->getValid(); + expired->t1_ = subnet->getT1(); + expired->t2_ = subnet->getT2(); + expired->cltt_ = time(NULL); + expired->subnet_id_ = subnet->getID(); + expired->fixed_ = false; + expired->hostname_ = std::string(""); + expired->fqdn_fwd_ = false; + expired->fqdn_rev_ = false; + + /// @todo: log here that the lease was reused (there's ticket #2524 for + /// logging in libdhcpsrv) + + if (!fake_allocation) { + // for REQUEST we do update the lease + LeaseMgrFactory::instance().updateLease4(expired); + } + + // We do nothing for SOLICIT. We'll just update database when + // the client gets back to us with REQUEST message. + + // it's not really expired at this stage anymore - let's return it as + // an updated lease + return (expired); +} + Lease6Ptr AllocEngine::createLease6(const Subnet6Ptr& subnet, const DuidPtr& duid, uint32_t iaid, @@ -338,6 +485,48 @@ Lease6Ptr AllocEngine::createLease6(const Subnet6Ptr& subnet, } } +Lease4Ptr AllocEngine::createLease4(const SubnetPtr& subnet, + const DuidPtr& clientid, + const HWAddrPtr& hwaddr, + const IOAddress& addr, + bool fake_allocation /*= false */ ) { + if (!hwaddr) { + isc_throw(BadValue, "Can't create a lease with NULL HW address"); + } + time_t now = time(NULL); + Lease4Ptr lease(new Lease4(addr, &hwaddr->hwaddr_[0], hwaddr->hwaddr_.size(), + &clientid->getDuid()[0], clientid->getDuid().size(), + subnet->getValid(), subnet->getT1(), subnet->getT2(), + now, subnet->getID())); + + if (!fake_allocation) { + // That is a real (REQUEST) allocation + bool status = LeaseMgrFactory::instance().addLease(lease); + + if (status) { + return (lease); + } else { + // One of many failures with LeaseMgr (e.g. lost connection to the + // database, database failed etc.). One notable case for that + // is that we are working in multi-process mode and we lost a race + // (some other process got that address first) + return (Lease4Ptr()); + } + } else { + // That is only fake (DISCOVER) allocation + + // It is for OFFER only. We should not insert the lease into LeaseMgr, + // but rather check that we could have inserted it. + Lease4Ptr existing = LeaseMgrFactory::instance().getLease4(addr); + if (!existing) { + return (lease); + } else { + return (Lease4Ptr()); + } + } +} + + AllocEngine::~AllocEngine() { // no need to delete allocator. smart_ptr will do the trick for us } diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h index 3ddf82956c..5f9994c663 100644 --- a/src/lib/dhcpsrv/alloc_engine.h +++ b/src/lib/dhcpsrv/alloc_engine.h @@ -17,6 +17,7 @@ #include #include +#include #include #include @@ -182,13 +183,15 @@ protected: /// /// @param subnet subnet the allocation should come from /// @param clientid Client identifier + /// @param hwaddr client's hardware address info /// @param hint a hint that the client provided /// @param fake_allocation is this real i.e. REQUEST (false) or just picking /// an address for DISCOVER that is not really allocated (true) /// @return Allocated IPv4 lease (or NULL if allocation failed) Lease4Ptr allocateAddress4(const SubnetPtr& subnet, - const DuidPtr& clientid, + const ClientIdPtr& clientid, + const HWAddrPtr& hwaddr, const isc::asiolink::IOAddress& hint, bool fake_allocation); @@ -224,12 +227,14 @@ private: /// /// @param subnet subnet the lease is allocated from /// @param clientid client identifier + /// @param hwaddr client's hardware address /// @param addr an address that was selected and is confirmed to be available /// @param fake_allocation is this real i.e. REQUEST (false) or just picking /// an address for DISCOVER that is not really allocated (true) /// @return allocated lease (or NULL in the unlikely case of the lease just /// becomed unavailable) - Lease4Ptr createLease4(const Subnet4Ptr& subnet, const DuidPtr& clientid, + Lease4Ptr createLease4(const SubnetPtr& subnet, const DuidPtr& clientid, + const HWAddrPtr& hwaddr, const isc::asiolink::IOAddress& addr, bool fake_allocation = false); @@ -260,12 +265,14 @@ private: /// @param expired old, expired lease /// @param subnet subnet the lease is allocated from /// @param clientid client identifier + /// @param hwaddr client's hardware address /// @param fake_allocation is this real i.e. REQUEST (false) or just picking /// an address for DISCOVER that is not really allocated (true) /// @return refreshed lease /// @throw BadValue if trying to recycle lease that is still valid - Lease4Ptr reuseExpiredLease(Lease4Ptr& expired, const Subnet4Ptr& subnet, - const DuidPtr& clientid, + Lease4Ptr reuseExpiredLease(Lease4Ptr& expired, const SubnetPtr& subnet, + const ClientIdPtr& clientid, + const HWAddrPtr& hwaddr, bool fake_allocation = false); /// @brief reuses expired IPv6 lease diff --git a/src/lib/dhcpsrv/lease_mgr.h b/src/lib/dhcpsrv/lease_mgr.h index cccea101e1..380f3fef3f 100644 --- a/src/lib/dhcpsrv/lease_mgr.h +++ b/src/lib/dhcpsrv/lease_mgr.h @@ -233,10 +233,10 @@ struct Lease4 : public Lease { /// @param valid_lft Lifetime of the lease /// @param cltt Client last transmission time /// @param subnet_id Subnet identification - Lease4(uint32_t addr, const uint8_t* hwaddr, size_t hwaddr_len, + Lease4(const isc::asiolink::IOAddress& addr, const uint8_t* hwaddr, size_t hwaddr_len, const uint8_t* clientid, size_t clientid_len, uint32_t valid_lft, - time_t cltt, uint32_t subnet_id) - : Lease(addr, 0, 0, valid_lft, subnet_id, cltt), + uint32_t t1, uint32_t t2, time_t cltt, uint32_t subnet_id) + : Lease(addr, t1, t2, valid_lft, subnet_id, cltt), ext_(0), hwaddr_(hwaddr, hwaddr + hwaddr_len), client_id_(new ClientId(clientid, clientid_len)) { } @@ -370,6 +370,7 @@ typedef std::vector Lease6Collection; class LeaseMgr { public: /// Client hardware address + /// @todo: migrate to HWAddr structure typedef std::vector HWAddr; /// Database configuration parameter map diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.cc b/src/lib/dhcpsrv/memfile_lease_mgr.cc index 79cf1c76eb..f2daf36317 100644 --- a/src/lib/dhcpsrv/memfile_lease_mgr.cc +++ b/src/lib/dhcpsrv/memfile_lease_mgr.cc @@ -13,6 +13,7 @@ // PERFORMANCE OF THIS SOFTWARE. #include +#include #include @@ -27,8 +28,13 @@ Memfile_LeaseMgr::Memfile_LeaseMgr(const ParameterMap& parameters) Memfile_LeaseMgr::~Memfile_LeaseMgr() { } -bool Memfile_LeaseMgr::addLease(const Lease4Ptr&) { - return (false); +bool Memfile_LeaseMgr::addLease(const Lease4Ptr& lease) { + if (getLease4(lease->addr_)) { + // there is a lease with specified address already + return (false); + } + storage4_.insert(lease); + return (true); } bool Memfile_LeaseMgr::addLease(const Lease6Ptr& lease) { @@ -40,27 +46,51 @@ bool Memfile_LeaseMgr::addLease(const Lease6Ptr& lease) { return (true); } -Lease4Ptr Memfile_LeaseMgr::getLease4(const isc::asiolink::IOAddress&) const { - return (Lease4Ptr()); +Lease4Ptr Memfile_LeaseMgr::getLease4(const isc::asiolink::IOAddress& addr) const { + Lease4Storage::iterator l = storage4_.find(addr); + if (l == storage4_.end()) { + return (Lease4Ptr()); + } else { + return (*l); + } } Lease4Collection Memfile_LeaseMgr::getLease4(const HWAddr& ) const { + isc_throw(NotImplemented, "getLease4(HWaddr x) method not implemented yet"); return (Lease4Collection()); } -Lease4Ptr Memfile_LeaseMgr::getLease4(const HWAddr&, - SubnetID) const { +Lease4Ptr Memfile_LeaseMgr::getLease4(const HWAddr& hwaddr, + SubnetID id) const { + Lease4Storage::iterator l; + for (l = storage4_.begin(); l != storage4_.end(); ++l) { + if ( ((*l)->hwaddr_ == hwaddr) && + ((*l)->subnet_id_ == id)) { + return (*l); + } + } + + // not found return (Lease4Ptr()); } -Lease4Ptr Memfile_LeaseMgr::getLease4(const ClientId&, - SubnetID) const { +Lease4Ptr Memfile_LeaseMgr::getLease4(const ClientId& client_id, + SubnetID subnet_id) const { + Lease4Storage::iterator l; + for (l = storage4_.begin(); l != storage4_.end(); ++l) { + if ( (*(*l)->client_id_ == client_id) && + ((*l)->subnet_id_ == subnet_id)) { + return (*l); + } + } + + // not found return (Lease4Ptr()); } Lease4Collection Memfile_LeaseMgr::getLease4(const ClientId& ) const { - return (Lease4Collection()); + isc_throw(NotImplemented, "getLease4(ClientId) not implemented"); } Lease6Ptr Memfile_LeaseMgr::getLease6(const isc::asiolink::IOAddress& addr) const { @@ -98,11 +128,18 @@ void Memfile_LeaseMgr::updateLease6(const Lease6Ptr& ) { bool Memfile_LeaseMgr::deleteLease(const isc::asiolink::IOAddress& addr) { if (addr.isV4()) { - // V4 not implemented yet - return (false); + // v4 lease + Lease4Storage::iterator l = storage4_.find(addr); + if (l == storage4_.end()) { + // No such lease + return (false); + } else { + storage4_.erase(l); + return (true); + } } else { - // V6 lease + // v6 lease Lease6Storage::iterator l = storage6_.find(addr); if (l == storage6_.end()) { // No such lease diff --git a/src/lib/dhcpsrv/memfile_lease_mgr.h b/src/lib/dhcpsrv/memfile_lease_mgr.h index 892d3ba21f..607b989923 100644 --- a/src/lib/dhcpsrv/memfile_lease_mgr.h +++ b/src/lib/dhcpsrv/memfile_lease_mgr.h @@ -231,6 +231,22 @@ protected: > > Lease6Storage; // Let the whole contraption be called Lease6Storage. + typedef boost::multi_index_container< // this is a multi-index container... + Lease4Ptr, // it will hold shared_ptr to leases6 + boost::multi_index::indexed_by< // and will be sorted by + // IPv6 address that are unique. That particular key is a member + // of the Lease6 structure, is of type IOAddress and can be accessed + // by doing &Lease6::addr_ + boost::multi_index::ordered_unique< + boost::multi_index::member + > + > + > Lease4Storage; // Let the whole contraption be called Lease6Storage. + + /// @brief stores IPv4 leases + Lease4Storage storage4_; + + /// @brief stores IPv6 leases Lease6Storage storage6_; }; diff --git a/src/lib/dhcpsrv/mysql_lease_mgr.cc b/src/lib/dhcpsrv/mysql_lease_mgr.cc index ad6e66c006..4468094c6a 100644 --- a/src/lib/dhcpsrv/mysql_lease_mgr.cc +++ b/src/lib/dhcpsrv/mysql_lease_mgr.cc @@ -448,9 +448,10 @@ public: time_t cltt = 0; MySqlLeaseMgr::convertFromDatabaseTime(expire_, valid_lifetime_, cltt); + // note that T1 and T2 are not stored return (Lease4Ptr(new Lease4(addr4_, hwaddr_buffer_, hwaddr_length_, client_id_buffer_, client_id_length_, - valid_lifetime_, cltt, subnet_id_))); + valid_lifetime_, 0, 0, cltt, subnet_id_))); } /// @brief Return columns in error diff --git a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc index 1183c0f3c5..7ce7d6ef86 100644 --- a/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc +++ b/src/lib/dhcpsrv/tests/alloc_engine_unittest.cc @@ -16,6 +16,7 @@ #include #include +#include #include #include #include @@ -50,11 +51,10 @@ public: using AllocEngine::IterativeAllocator; }; -// empty class for now, but may be extended once Addr6 becomes bigger -class AllocEngineTest : public ::testing::Test { +class AllocEngine6Test : public ::testing::Test { public: - AllocEngineTest() { - duid_ = boost::shared_ptr(new DUID(vector(8, 0x42))); + AllocEngine6Test() { + duid_ = DuidPtr(new DUID(vector(8, 0x42))); iaid_ = 42; // instantiate cfg_mgr @@ -88,7 +88,7 @@ public: // @todo: check cltt } - ~AllocEngineTest() { + ~AllocEngine6Test() { factory_.destroy(); } @@ -99,9 +99,59 @@ public: LeaseMgrFactory factory_; }; +class AllocEngine4Test : public ::testing::Test { +public: + AllocEngine4Test() { + clientid_ = ClientIdPtr(new ClientId(vector(8, 0x44))); + static uint8_t mac[] = { 0, 1, 22, 33, 44, 55}; + + // Let's use odd hardware type to check if there is no Ethernet + // hardcoded anywhere. + hwaddr_ = HWAddrPtr(new HWAddr(mac, sizeof(mac), HTYPE_FDDI)); + + // instantiate cfg_mgr + CfgMgr& cfg_mgr = CfgMgr::instance(); + + subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3)); + pool_ = Pool4Ptr(new Pool4(IOAddress("192.0.2.100"), + IOAddress("192.0.2.109"))); + subnet_->addPool(pool_); + cfg_mgr.addSubnet4(subnet_); + + factory_.create("type=memfile"); + } + + void checkLease4(const Lease4Ptr& lease) { + // that is belongs to the right subnet + EXPECT_EQ(lease->subnet_id_, subnet_->getID()); + EXPECT_TRUE(subnet_->inRange(lease->addr_)); + EXPECT_TRUE(subnet_->inPool(lease->addr_)); + + // that it have proper parameters + EXPECT_EQ(subnet_->getValid(), lease->valid_lft_); + EXPECT_EQ(subnet_->getT1(), lease->t1_); + EXPECT_EQ(subnet_->getT2(), lease->t2_); + EXPECT_TRUE(false == lease->fqdn_fwd_); + EXPECT_TRUE(false == lease->fqdn_rev_); + EXPECT_TRUE(*lease->client_id_ == *clientid_); + EXPECT_TRUE(lease->hwaddr_ == hwaddr_->hwaddr_); + // @todo: check cltt + } + + ~AllocEngine4Test() { + factory_.destroy(); + } + + ClientIdPtr clientid_; + HWAddrPtr hwaddr_; + Subnet4Ptr subnet_; + Pool4Ptr pool_; + LeaseMgrFactory factory_; +}; + // This test checks if the Allocation Engine can be instantiated and that it // parses parameters string properly. -TEST_F(AllocEngineTest, constructor) { +TEST_F(AllocEngine6Test, constructor) { boost::scoped_ptr x; // Hashed and random allocators are not supported yet @@ -112,7 +162,7 @@ TEST_F(AllocEngineTest, constructor) { } // This test checks if the simple allocation can succeed -TEST_F(AllocEngineTest, simpleAlloc) { +TEST_F(AllocEngine6Test, simpleAlloc6) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100))); ASSERT_TRUE(engine); @@ -135,7 +185,7 @@ TEST_F(AllocEngineTest, simpleAlloc) { } // This test checks if the fake allocation (for SOLICIT) can succeed -TEST_F(AllocEngineTest, fakeAlloc) { +TEST_F(AllocEngine6Test, fakeAlloc6) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100))); ASSERT_TRUE(engine); @@ -156,7 +206,7 @@ TEST_F(AllocEngineTest, fakeAlloc) { // This test checks if the allocation with a hint that is valid (in range, // in pool and free) can succeed -TEST_F(AllocEngineTest, allocWithValidHint) { +TEST_F(AllocEngine6Test, allocWithValidHint6) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100))); ASSERT_TRUE(engine); @@ -184,15 +234,16 @@ TEST_F(AllocEngineTest, allocWithValidHint) { // This test checks if the allocation with a hint that is in range, // in pool, but is currently used) can succeed -TEST_F(AllocEngineTest, allocWithUsedHint) { +TEST_F(AllocEngine6Test, allocWithUsedHint6) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100))); ASSERT_TRUE(engine); // let's create a lease and put it in the LeaseMgr DuidPtr duid2 = boost::shared_ptr(new DUID(vector(8, 0xff))); + time_t now = time(NULL); Lease6Ptr used(new Lease6(Lease6::LEASE_IA_NA, IOAddress("2001:db8:1::1f"), - duid2, 1, 2, 3, 4, 5, subnet_->getID())); + duid2, 1, 2, 3, 4, now, subnet_->getID())); ASSERT_TRUE(LeaseMgrFactory::instance().addLease(used)); // another client comes in and request an address that is in pool, but @@ -223,7 +274,7 @@ TEST_F(AllocEngineTest, allocWithUsedHint) { // This test checks if the allocation with a hint that is out the blue // can succeed. The invalid hint should be ignored completely. -TEST_F(AllocEngineTest, allocBogusHint) { +TEST_F(AllocEngine6Test, allocBogusHint6) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100))); ASSERT_TRUE(engine); @@ -253,7 +304,7 @@ TEST_F(AllocEngineTest, allocBogusHint) { // This test verifies that the allocator picks addresses that belong to the // pool -TEST_F(AllocEngineTest, IterativeAllocator) { +TEST_F(AllocEngine6Test, IterativeAllocator) { boost::scoped_ptr alloc(new NakedAllocEngine::IterativeAllocator()); @@ -267,7 +318,7 @@ TEST_F(AllocEngineTest, IterativeAllocator) { // This test verifies that the iterative allocator really walks over all addresses // in all pools in specified subnet. It also must not pick the same address twice // unless it runs out of pool space and must start over. -TEST_F(AllocEngineTest, IterativeAllocator_manyPools) { +TEST_F(AllocEngine6Test, IterativeAllocator_manyPools6) { NakedAllocEngine::IterativeAllocator* alloc = new NakedAllocEngine::IterativeAllocator(); // let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already. @@ -322,7 +373,7 @@ TEST_F(AllocEngineTest, IterativeAllocator_manyPools) { } // This test checks if really small pools are working -TEST_F(AllocEngineTest, smallPool) { +TEST_F(AllocEngine6Test, smallPool6) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100))); ASSERT_TRUE(engine); @@ -358,7 +409,7 @@ TEST_F(AllocEngineTest, smallPool) { // This test checks if all addresses in a pool are currently used, the attempt // to find out a new lease fails. -TEST_F(AllocEngineTest, outOfAddresses) { +TEST_F(AllocEngine6Test, outOfAddresses6) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100))); ASSERT_TRUE(engine); @@ -389,7 +440,7 @@ TEST_F(AllocEngineTest, outOfAddresses) { } // This test checks if an expired lease can be reused in SOLICIT (fake allocation) -TEST_F(AllocEngineTest, solicitReuseExpiredLease) { +TEST_F(AllocEngine6Test, solicitReuseExpiredLease6) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100))); ASSERT_TRUE(engine); @@ -413,6 +464,9 @@ TEST_F(AllocEngineTest, solicitReuseExpiredLease) { lease->valid_lft_ = 495; // Lease was valid for 495 seconds ASSERT_TRUE(LeaseMgrFactory::instance().addLease(lease)); + // Make sure that we really created expired lease + ASSERT_TRUE(lease->expired()); + // CASE 1: Asking for any address lease = engine->allocateAddress6(subnet_, duid_, iaid_, IOAddress("::"), true); @@ -432,7 +486,7 @@ TEST_F(AllocEngineTest, solicitReuseExpiredLease) { } // This test checks if an expired lease can be reused in REQUEST (actual allocation) -TEST_F(AllocEngineTest, requestReuseExpiredLease) { +TEST_F(AllocEngine6Test, requestReuseExpiredLease6) { boost::scoped_ptr engine; ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100))); ASSERT_TRUE(engine); @@ -473,4 +527,372 @@ TEST_F(AllocEngineTest, requestReuseExpiredLease) { detailCompareLease(lease, from_mgr); } +// --- IPv4 --- + +// This test checks if the simple IPv4 allocation can succeed +TEST_F(AllocEngine4Test, simpleAlloc4) { + boost::scoped_ptr engine; + ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100))); + ASSERT_TRUE(engine); + + Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, + IOAddress("0.0.0.0"), false); + + // 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); +} + +// This test checks if the fake allocation (for DISCOVER) can succeed +TEST_F(AllocEngine4Test, fakeAlloc4) { + boost::scoped_ptr engine; + ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100))); + ASSERT_TRUE(engine); + + Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, + IOAddress("0.0.0.0"), true); + + // 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); +} + + +// 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(AllocEngine::ALLOC_ITERATIVE, 100))); + ASSERT_TRUE(engine); + + Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, + IOAddress("192.0.2.105"), + false); + + // check that we got a lease + ASSERT_TRUE(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(AllocEngine::ALLOC_ITERATIVE, 100))); + ASSERT_TRUE(engine); + + // let's create a lease and put it in the LeaseMgr + uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe}; + 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, sizeof(hwaddr2), + clientid2, sizeof(clientid2), 1, 2, 3, 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. + Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, + IOAddress("192.0.2.106"), + false); + // check that we got a lease + ASSERT_TRUE(lease); + + // allocated address must be different + EXPECT_TRUE(used->addr_.toText() != lease->addr_.toText()); + + // we should NOT get what we asked for, because it is used already + EXPECT_TRUE(lease->addr_.toText() != "192.0.2.106"); + + // 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 out 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(AllocEngine::ALLOC_ITERATIVE, 100))); + ASSERT_TRUE(engine); + + // Client would like to get a 3000::abc lease, which does not belong to any + // supported lease. Allocation engine should ignore it and carry on + // with the normal allocation + Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, + IOAddress("10.1.1.1"), + false); + // check that we got a lease + ASSERT_TRUE(lease); + + // we should NOT get what we asked for, because it is used already + EXPECT_TRUE(lease->addr_.toText() != "10.1.1.1"); + + // 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 verifies that the allocator picks addresses that belong to the +// pool +TEST_F(AllocEngine4Test, IterativeAllocator) { + boost::scoped_ptr + alloc(new NakedAllocEngine::IterativeAllocator()); + + for (int i = 0; i < 1000; ++i) { + IOAddress candidate = alloc->pickAddress(subnet_, clientid_, + IOAddress("0.0.0.0")); + EXPECT_TRUE(subnet_->inPool(candidate)); + } +} + + +// This test verifies that the iterative allocator really walks over all addresses +// in all pools in specified subnet. It also must not pick the same address twice +// unless it runs out of pool space and must start over. +TEST_F(AllocEngine4Test, IterativeAllocator_manyPools4) { + NakedAllocEngine::IterativeAllocator* alloc = new NakedAllocEngine::IterativeAllocator(); + + // let's start from 2, as there is 2001:db8:1::10 - 2001:db8:1::20 pool already. + for (int i = 2; i < 10; ++i) { + stringstream min, max; + + min << "192.0.2." << i*10 + 1; + max << "192.0.2." << i*10 + 9; + + Pool4Ptr pool(new Pool4(IOAddress(min.str()), + IOAddress(max.str()))); + // cout << "Adding pool: " << min.str() << "-" << max.str() << endl; + subnet_->addPool(pool); + } + + int total = 10 + 8*9; // first pool (.100 - .109) has 10 addresses in it, + // there are 8 extra pools with 9 addresses in each. + + // Let's keep picked addresses here and check their uniqueness. + std::map generated_addrs; + int cnt = 0; + while (++cnt) { + IOAddress candidate = alloc->pickAddress(subnet_, clientid_, IOAddress("0.0.0.0")); + EXPECT_TRUE(subnet_->inPool(candidate)); + + // One way to easily verify that the iterative allocator really works is + // to uncomment the following line and observe its output that it + // covers all defined subnets. + // cout << candidate.toText() << endl; + + if (generated_addrs.find(candidate) == generated_addrs.end()) { + // we haven't had this + generated_addrs[candidate] = 0; + } else { + // we have seen this address before. That should mean that we + // iterated over all addresses. + if (generated_addrs.size() == total) { + // we have exactly the number of address in all pools + break; + } + ADD_FAILURE() << "Too many or not enough unique addresses generated."; + break; + } + + if ( cnt>total ) { + ADD_FAILURE() << "Too many unique addresses generated."; + break; + } + } + + delete alloc; +} + + +// This test checks if really small pools are working +TEST_F(AllocEngine4Test, smallPool4) { + boost::scoped_ptr engine; + ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100))); + ASSERT_TRUE(engine); + + IOAddress addr("192.0.2.17"); + CfgMgr& cfg_mgr = CfgMgr::instance(); + cfg_mgr.deleteSubnets4(); // Get rid of the default test configuration + + // Create configuration similar to other tests, but with a single address pool + subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3)); + pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address + subnet_->addPool(pool_); + cfg_mgr.addSubnet4(subnet_); + + Lease4Ptr lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), + false); + + // Check that we got that single lease + ASSERT_TRUE(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(AllocEngine::ALLOC_ITERATIVE, 100))); + ASSERT_TRUE(engine); + + IOAddress addr("192.0.2.17"); + CfgMgr& cfg_mgr = CfgMgr::instance(); + cfg_mgr.deleteSubnets4(); // Get rid of the default test configuration + + // Create configuration similar to other tests, but with a single address pool + subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3)); + pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address + subnet_->addPool(pool_); + cfg_mgr.addSubnet4(subnet_); + + // Just a different hw/client-id for the second client + uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe}; + uint8_t clientid2[] = { 8, 7, 6, 5, 4, 3, 2, 1 }; + time_t now = time(NULL); + Lease4Ptr lease(new Lease4(addr, hwaddr2, sizeof(hwaddr2), clientid2, sizeof(clientid2), + 501, 502, 503, 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 + + EXPECT_THROW(engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"),false), + AllocFailed); +} + +// This test checks if an expired lease can be reused in DISCOVER (fake allocation) +TEST_F(AllocEngine4Test, discoverReuseExpiredLease4) { + boost::scoped_ptr engine; + ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100))); + ASSERT_TRUE(engine); + + IOAddress addr("192.0.2.15"); + CfgMgr& cfg_mgr = CfgMgr::instance(); + cfg_mgr.deleteSubnets4(); // Get rid of the default test configuration + + // Create configuration similar to other tests, but with a single address pool + subnet_ = Subnet4Ptr(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3)); + pool_ = Pool4Ptr(new Pool4(addr, addr)); // just a single address + subnet_->addPool(pool_); + cfg_mgr.addSubnet4(subnet_); + + // Just a different hw/client-id for the second client + uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe}; + 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, clientid2, sizeof(clientid2), hwaddr2, sizeof(hwaddr2), + 495, 100, 200, now, subnet_->getID())); + // 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 + lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress("0.0.0.0"), + true); + // Check that we got that single lease + ASSERT_TRUE(lease); + EXPECT_EQ(addr.toText(), lease->addr_.toText()); + + // Do all checks on the lease (if subnet-id, preferred/valid times are ok etc.) + checkLease4(lease); + + // CASE 2: Asking specifically for this address + lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, IOAddress(addr.toText()), + true); + // Check that we got that single lease + ASSERT_TRUE(lease); + EXPECT_EQ(addr.toText(), lease->addr_.toText()); +} + +// 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(AllocEngine::ALLOC_ITERATIVE, 100))); + ASSERT_TRUE(engine); + + IOAddress addr("192.0.2.105"); + + // Just a different hw/client-id for the second client + uint8_t hwaddr2[] = { 0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfe}; + 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, clientid2, sizeof(clientid2), hwaddr2, sizeof(hwaddr2), + 495, 100, 200, now, subnet_->getID())); + // 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 + lease = engine->allocateAddress4(subnet_, clientid_, hwaddr_, + IOAddress(addr.toText()), false); + + // Check that he got that single lease + ASSERT_TRUE(lease); + EXPECT_EQ(addr.toText(), lease->addr_.toText()); + + // 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); +} + }; // end of anonymous namespace diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc index 7b23bcf409..09421c101c 100644 --- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc @@ -39,9 +39,14 @@ namespace { class CfgMgrTest : public ::testing::Test { public: CfgMgrTest() { + // make sure we start with a clean configuration + CfgMgr::instance().deleteSubnets4(); + CfgMgr::instance().deleteSubnets6(); } ~CfgMgrTest() { + // clean up after the test + CfgMgr::instance().deleteSubnets4(); CfgMgr::instance().deleteSubnets6(); } }; diff --git a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc index 38c955583c..8d8c7f85bd 100644 --- a/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc +++ b/src/lib/dhcpsrv/tests/lease_mgr_unittest.cc @@ -272,7 +272,7 @@ TEST(Lease4, Lease4Constructor) { // Create the lease Lease4 lease(ADDRESS[i], HWADDR, sizeof(HWADDR), - CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, + CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, 0, 0, current_time, SUBNET_ID); EXPECT_EQ(ADDRESS[i], static_cast(lease.addr_)); @@ -312,10 +312,10 @@ TEST(Lease4, OperatorEquals) { // Check when the leases are equal. Lease4 lease1(ADDRESS, HWADDR, sizeof(HWADDR), - CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, + CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, 0, 0, SUBNET_ID); Lease4 lease2(ADDRESS, HWADDR, sizeof(HWADDR), - CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, + CLIENTID, sizeof(CLIENTID), VALID_LIFETIME, current_time, 0, 0, SUBNET_ID); EXPECT_TRUE(lease1 == lease2); EXPECT_FALSE(lease1 != lease2); diff --git a/tests/tools/perfdhcp/test_control.cc b/tests/tools/perfdhcp/test_control.cc index 9376972616..01f864f11a 100644 --- a/tests/tools/perfdhcp/test_control.cc +++ b/tests/tools/perfdhcp/test_control.cc @@ -1414,9 +1414,7 @@ TestControl::sendRequest4(const TestControlSocket& socket, setDefaults4(socket, pkt4); // Set hardware address - const uint8_t* chaddr = offer_pkt4->getChaddr(); - std::vector mac_address(chaddr, chaddr + HW_ETHER_LEN); - pkt4->setHWAddr(HTYPE_ETHER, mac_address.size(), mac_address); + pkt4->setHWAddr(offer_pkt4->getHWAddr()); // Set elapsed time. uint32_t elapsed_time = getElapsedTime(discover_pkt4, offer_pkt4); pkt4->setSecs(static_cast(elapsed_time / 1000)); @@ -1461,8 +1459,10 @@ TestControl::sendRequest4(const TestControlSocket& socket, transid)); // Set hardware address from OFFER packet received. - const uint8_t* chaddr = offer_pkt4->getChaddr(); - std::vector mac_address(chaddr, chaddr + HW_ETHER_LEN); + HWAddrPtr hwaddr = offer_pkt4->getHWAddr(); + std::vector mac_address(HW_ETHER_LEN, 0); + uint8_t hw_len = hwaddr->hwaddr_.size(); + memcpy(&mac_address[0], &hwaddr->hwaddr_[0], hw_len > 16 ? 16 : hw_len); pkt4->writeAt(rand_offset, mac_address.begin(), mac_address.end()); // Set elapsed time.