From: Thomas Markwalder Date: Fri, 17 Aug 2018 18:27:57 +0000 (-0400) Subject: [#13,!6] allocation engine modifications to support v6 global host reservations X-Git-Tag: gitlab20_base~17^2~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2a90f95de03546c9bd2409fdd65447e077caf934;p=thirdparty%2Fkea.git [#13,!6] allocation engine modifications to support v6 global host reservations src/lib/dhcpsrv/alloc_engine.cc New functions: AllocEngine::ClientContext6::globalHost() AllocEngine::ClientContext6::hasGlobalReservation() AllocEngine::findGlobalReservation() AllocEngine::allocateGlobalReservedLeases6() Modified functions: AllocEngine::ClientContext6::currentHost() - modified to take into account a global host AllocEngine::findReservation() - modified to use findGlobalReservation() AllocEngine::allocateLeases6() - modified to use allocateGlobalReservation() AllocEngine::removeNonmatchingReservedLeases6() - modified to retain global reservations AllocEngine::extendLease6() - modified to bypass range and client mismatch disqualification for global reservations src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc TEST_F(AllocEngine6Test, globalHostDynamicAddress) TEST_F(AllocEngine6Test, globalHostReservedAddress) TEST_F(AllocEngine6Test, globalHostReservedPrefix) src/lib/dhcpsrv/tests/alloc_engine_utils.cc testStatistics() - changed to test against SUBNET_ID_UNSUSED src/lib/dhcpsrv/tests/alloc_engine_utils.h testStatistics() - changed subnet_id default --- diff --git a/src/lib/dhcpsrv/alloc_engine.cc b/src/lib/dhcpsrv/alloc_engine.cc index 90b9058de4..39aaabf604 100644 --- a/src/lib/dhcpsrv/alloc_engine.cc +++ b/src/lib/dhcpsrv/alloc_engine.cc @@ -483,14 +483,37 @@ ConstHostPtr AllocEngine::ClientContext6::currentHost() const { Subnet6Ptr subnet = host_subnet_ ? host_subnet_ : subnet_; if (subnet) { - auto host = hosts_.find(subnet->getID()); + SubnetID id = (subnet_->getHostReservationMode() == Network::HR_GLOBAL ? + SUBNET_ID_GLOBAL : subnet->getID()); + + auto host = hosts_.find(id); + if (host != hosts_.cend()) { + return (host->second); + } + } + + return (ConstHostPtr()); +} + +ConstHostPtr +AllocEngine::ClientContext6::globalHost() const { + Subnet6Ptr subnet = host_subnet_ ? host_subnet_ : subnet_; + if (subnet && subnet_->getHostReservationMode() == Network::HR_GLOBAL) { + auto host = hosts_.find(SUBNET_ID_GLOBAL); if (host != hosts_.cend()) { return (host->second); } } + return (ConstHostPtr()); } +bool +AllocEngine::ClientContext6::hasGlobalReservation(const IPv6Resrv& resv) const { + ConstHostPtr ghost = globalHost(); + return (ghost && ghost->hasReservation(resv)); +} + void AllocEngine::findReservation(ClientContext6& ctx) { ctx.hosts_.clear(); @@ -505,6 +528,17 @@ void AllocEngine::findReservation(ClientContext6& ctx) { SharedNetwork6Ptr network; subnet->getSharedNetwork(network); + if (subnet->getHostReservationMode() == Network::HR_GLOBAL) { + ConstHostPtr ghost = findGlobalReservation(ctx); + if (ghost) { + ctx.hosts_[SUBNET_ID_GLOBAL] = ghost; + + // @todo In theory, to support global as part of HR_ALL, + // we would just keep going, instead of returning. + return; + } + } + // If the subnet belongs to a shared network it is usually going to be // more efficient to make a query for all reservations for a particular // client rather than a query for each subnet within this shared network. @@ -567,6 +601,24 @@ void AllocEngine::findReservation(ClientContext6& ctx) { } } +ConstHostPtr +AllocEngine::findGlobalReservation(ClientContext6& ctx) { + ConstHostPtr host; + BOOST_FOREACH(const IdentifierPair& id_pair, ctx.host_identifiers_) { + // Attempt to find a host using a specified identifier. + host = HostMgr::instance().get6(SUBNET_ID_GLOBAL, id_pair.first, + &id_pair.second[0], id_pair.second.size()); + + // If we found matching global host we're done. + if (host) { + break; + } + } + + return (host); +} + + Lease6Collection AllocEngine::allocateLeases6(ClientContext6& ctx) { @@ -1020,7 +1072,6 @@ void AllocEngine::allocateReservedLeases6(ClientContext6& ctx, Lease6Collection& existing_leases) { - // If there are no reservations or the reservation is v4, there's nothing to do. if (ctx.hosts_.empty()) { LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, @@ -1029,6 +1080,11 @@ AllocEngine::allocateReservedLeases6(ClientContext6& ctx, return; } + if (allocateGlobalReservedLeases6(ctx, existing_leases)) { + // global reservation provided the lease, we're done + return; + } + // Let's convert this from Lease::Type to IPv6Reserv::Type IPv6Resrv::Type type = ctx.currentIA().type_ == Lease::TYPE_NA ? IPv6Resrv::TYPE_NA : IPv6Resrv::TYPE_PD; @@ -1039,8 +1095,7 @@ AllocEngine::allocateReservedLeases6(ClientContext6& ctx, BOOST_FOREACH(const Lease6Ptr& lease, existing_leases) { if ((lease->valid_lft_ != 0)) { if ((ctx.hosts_.count(lease->subnet_id_) > 0) && - ctx.hosts_[lease->subnet_id_]->hasReservation(IPv6Resrv(type, lease->addr_, - lease->prefixlen_))) { + ctx.hosts_[lease->subnet_id_]->hasReservation(makeIPv6Resrv(*lease))) { // We found existing lease for a reserved address or prefix. // We'll simply extend the lifetime of the lease. LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, @@ -1188,6 +1243,126 @@ AllocEngine::allocateReservedLeases6(ClientContext6& ctx, } } +bool +AllocEngine::allocateGlobalReservedLeases6(ClientContext6& ctx, + Lease6Collection& existing_leases) { + // Get the global host + ConstHostPtr ghost = ctx.globalHost(); + if (!ghost) { + return (false); + } + + // We want to avoid allocating new lease for an IA if there is already + // a valid lease for which client has reservation. So, we first check if + // we already have a lease for a reserved address or prefix. + BOOST_FOREACH(const Lease6Ptr& lease, existing_leases) { + if ((lease->valid_lft_ != 0) && + (ghost->hasReservation(makeIPv6Resrv(*lease)))) { + // We found existing lease for a reserved address or prefix. + // We'll simply extend the lifetime of the lease. + LOG_DEBUG(alloc_engine_logger, ALLOC_ENGINE_DBG_TRACE, + ALLOC_ENGINE_V6_ALLOC_HR_LEASE_EXISTS) + .arg(ctx.query_->getLabel()) + .arg(lease->typeToText(lease->type_)) + .arg(lease->addr_.toText()); + + // parameters, such as hostname. We want to hand out the hostname value + // from the same reservation entry as IP addresses. Thus, let's see if + // there is any hostname reservation. + if (!ghost->getHostname().empty()) { + // We have to determine whether the hostname is generated + // in response to client's FQDN or not. If yes, we will + // need to qualify the hostname. Otherwise, we just use + // the hostname as it is specified for the reservation. + OptionPtr fqdn = ctx.query_->getOption(D6O_CLIENT_FQDN); + ctx.hostname_ = CfgMgr::instance().getD2ClientMgr(). + qualifyName(ghost->getHostname(), static_cast(fqdn)); + } + + // If this is a real allocation, we may need to extend the lease + // lifetime. + if (!ctx.fake_allocation_ && conditionalExtendLifetime(*lease)) { + LeaseMgrFactory::instance().updateLease6(lease); + } + + return(true); + } + } + + // There is no lease for a reservation in this IA. So, let's now iterate + // over reservations specified and try to allocate one of them for the IA. + + // Let's convert this from Lease::Type to IPv6Reserv::Type + IPv6Resrv::Type type = ctx.currentIA().type_ == Lease::TYPE_NA ? + IPv6Resrv::TYPE_NA : IPv6Resrv::TYPE_PD; + + const IPv6ResrvRange& reservs = ghost->getIPv6Reservations(type); + BOOST_FOREACH(IPv6ResrvTuple type_lease_tuple, reservs) { + // We do have a reservation for address or prefix. + const IOAddress& addr = type_lease_tuple.second.getPrefix(); + uint8_t prefix_len = type_lease_tuple.second.getPrefixLen(); + + // We have allocated this address/prefix while processing one of the + // previous IAs, so let's try another reservation. + if (ctx.isAllocated(addr, prefix_len)) { + continue; + } + + // If there's a lease for this address, let's not create it. + // It doesn't matter whether it is for this client or for someone else. + if (!LeaseMgrFactory::instance().getLease6(ctx.currentIA().type_, addr)) { + + if (!ghost->getHostname().empty()) { + // If there is a hostname reservation here we should stick + // to this reservation. By updating the hostname in the + // context we make sure that the database is updated with + // this new value and the server doesn't need to do it and + // its processing performance is not impacted by the hostname + // updates. + + // We have to determine whether the hostname is generated + // in response to client's FQDN or not. If yes, we will + // need to qualify the hostname. Otherwise, we just use + // the hostname as it is specified for the reservation. + OptionPtr fqdn = ctx.query_->getOption(D6O_CLIENT_FQDN); + ctx.hostname_ = CfgMgr::instance().getD2ClientMgr(). + qualifyName(ghost->getHostname(), static_cast(fqdn)); + } + + // Ok, let's create a new lease... + CalloutHandle::CalloutNextStep callout_status = CalloutHandle::NEXT_STEP_CONTINUE; + Lease6Ptr lease = createLease6(ctx, addr, prefix_len, callout_status); + + // ... and add it to the existing leases list. + existing_leases.push_back(lease); + + if (ctx.currentIA().type_ == Lease::TYPE_NA) { + LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_HR_ADDR_GRANTED) + .arg(addr.toText()) + .arg(ctx.query_->getLabel()); + } else { + LOG_INFO(alloc_engine_logger, ALLOC_ENGINE_V6_HR_PREFIX_GRANTED) + .arg(addr.toText()) + .arg(static_cast(prefix_len)) + .arg(ctx.query_->getLabel()); + } + + // We found a lease for this client and this IA. Let's return. + // Returning after the first lease was assigned is useful if we + // have multiple reservations for the same client. If the client + // sends 2 IAs, the first time we call allocateReservedLeases6 will + // use the first reservation and return. The second time, we'll + // go over the first reservation, but will discover that there's + // a lease corresponding to it and will skip it and then pick + // the second reservation and turn it into the lease. This approach + // would work for any number of reservations. + return (true); + } + } + + return(false); +} + void AllocEngine::removeNonmatchingReservedLeases6(ClientContext6& ctx, Lease6Collection& existing_leases) { @@ -1210,19 +1385,12 @@ AllocEngine::removeNonmatchingReservedLeases6(ClientContext6& ctx, BOOST_FOREACH(const Lease6Ptr& candidate, copy) { // If we have reservation we should check if the reservation is for // the candidate lease. If so, we simply accept the lease. - if (ctx.hosts_.count(candidate->subnet_id_) > 0) { - if (candidate->type_ == Lease6::TYPE_NA) { - if (ctx.hosts_[candidate->subnet_id_]->hasReservation( - IPv6Resrv(IPv6Resrv::TYPE_NA, candidate->addr_))) { - continue; - } - } else { - if (ctx.hosts_[candidate->subnet_id_]->hasReservation( - IPv6Resrv(IPv6Resrv::TYPE_PD,candidate->addr_, - candidate->prefixlen_))) { - continue; - } - } + IPv6Resrv resv = makeIPv6Resrv(*candidate); + if ((ctx.hasGlobalReservation(resv)) || + ((ctx.hosts_.count(candidate->subnet_id_) > 0) && + (ctx.hosts_[candidate->subnet_id_]->hasReservation(resv)))) { + // We have a subnet reservation + continue; } // The candidate address doesn't appear to be reserved for us. @@ -1357,46 +1525,41 @@ AllocEngine::removeNonreservedLeases6(ClientContext6& ctx, for (Lease6Collection::iterator lease = existing_leases.begin(); lease != existing_leases.end(); ++lease) { - IPv6Resrv resv(ctx.currentIA().type_ == Lease::TYPE_NA ? - IPv6Resrv::TYPE_NA : IPv6Resrv::TYPE_PD, - (*lease)->addr_, (*lease)->prefixlen_); - - // If there is no reservation for this subnet. - if ((ctx.hosts_.count((*lease)->subnet_id_) > 0) && - (ctx.hosts_[(*lease)->subnet_id_]->hasReservation(resv))) { + // If there is reservation for this keep it. + IPv6Resrv resv = makeIPv6Resrv(*(*lease)); + if (ctx.hasGlobalReservation(resv) || + ((ctx.hosts_.count((*lease)->subnet_id_) > 0) && + (ctx.hosts_[(*lease)->subnet_id_]->hasReservation(resv)))) { continue; + } - } else { - - // We have reservations, but not for this lease. Release it. - - // Remove this lease from LeaseMgr - LeaseMgrFactory::instance().deleteLease((*lease)->addr_); + // We have reservations, but not for this lease. Release it. + // Remove this lease from LeaseMgr + LeaseMgrFactory::instance().deleteLease((*lease)->addr_); - // Update DNS if required. - queueNCR(CHG_REMOVE, *lease); + // Update DNS if required. + queueNCR(CHG_REMOVE, *lease); - // Need to decrease statistic for assigned addresses. - StatsMgr::instance().addValue( - StatsMgr::generateName("subnet", (*lease)->subnet_id_, - ctx.currentIA().type_ == Lease::TYPE_NA ? - "assigned-nas" : "assigned-pds"), - static_cast(-1)); + // Need to decrease statistic for assigned addresses. + StatsMgr::instance().addValue( + StatsMgr::generateName("subnet", (*lease)->subnet_id_, + ctx.currentIA().type_ == Lease::TYPE_NA ? + "assigned-nas" : "assigned-pds"), + static_cast(-1)); - /// @todo: Probably trigger a hook here + /// @todo: Probably trigger a hook here - // Add this to the list of removed leases. - ctx.currentIA().old_leases_.push_back(*lease); + // Add this to the list of removed leases. + ctx.currentIA().old_leases_.push_back(*lease); - // Set this pointer to NULL. The pointer is still valid. We're just - // setting the Lease6Ptr to NULL value. We'll remove all NULL - // pointers once the loop is finished. - lease->reset(); + // Set this pointer to NULL. The pointer is still valid. We're just + // setting the Lease6Ptr to NULL value. We'll remove all NULL + // pointers once the loop is finished. + lease->reset(); - if (--total == 1) { - // If there's only one lease left, break the loop. - break; - } + if (--total == 1) { + // If there's only one lease left, break the loop. + break; } } @@ -1740,10 +1903,11 @@ AllocEngine::extendLease6(ClientContext6& ctx, Lease6Ptr lease) { } } - // Check if the lease still belongs to the subnet and that the use of this subnet - // is allowed per client classification. If not, remove this lease. - if (((lease->type_ != Lease::TYPE_PD) && !ctx.subnet_->inRange(lease->addr_)) || - !ctx.subnet_->clientSupported(ctx.query_->getClasses())) { + // If the lease is not global and it is either out of range (NAs only) or it + // is not permitted by subnet client classification, delete it. + if (!(ctx.hasGlobalReservation(makeIPv6Resrv(*lease))) && + (((lease->type_ != Lease::TYPE_PD) && !ctx.subnet_->inRange(lease->addr_)) || + !ctx.subnet_->clientSupported(ctx.query_->getClasses()))) { // Oh dear, the lease is no longer valid. We need to get rid of it. // Remove this lease from LeaseMgr diff --git a/src/lib/dhcpsrv/alloc_engine.h b/src/lib/dhcpsrv/alloc_engine.h index 198e32c9cd..b806af2285 100644 --- a/src/lib/dhcpsrv/alloc_engine.h +++ b/src/lib/dhcpsrv/alloc_engine.h @@ -484,6 +484,23 @@ public: /// @return Pointer to the host object. ConstHostPtr currentHost() const; + /// @brief Returns global host reservation if there is one + /// + /// If the current subnet's reservation mode is global and + /// there is a global host (i.e. reservation belonging to + /// the global subnet), return it. Otherwise return an + /// empty pointer. + /// + /// @return Pointer to the host object. + ConstHostPtr globalHost() const; + + /// @brief Determines if a global reservation exists + /// + /// @return true if there current subnet's reservation mode is + /// global and there is global host containing the given + /// lease reservation, false otherwise + bool hasGlobalReservation(const IPv6Resrv& resv) const; + /// @brief Default constructor. ClientContext6(); @@ -747,6 +764,30 @@ public: /// @param ctx Client context that contains all necessary information. static void findReservation(ClientContext6& ctx); + /// @brief Attempts to find the host reservation for the client. + /// + /// This method attempts to find a "global" host reservation matching the + /// client identifier. It will return the first global reservation that + /// matches per the configured list of host identifiers, or an empty + /// pointer if no matches are found. + /// + /// @param ctx Client context holding various information about the client. + /// @return Pointer to the reservation found, or an empty pointer. + static ConstHostPtr findGlobalReservation(ClientContext6& ctx); + + /// @brief Creates an IPv6Resrv instance from a Lease6 + /// + /// @param lease Reference to the Lease6 + /// @return The newly formed IPv6Resrv instance + static IPv6Resrv makeIPv6Resrv(const Lease6& lease) { + if (lease.type_ == Lease::TYPE_NA) { + return (IPv6Resrv(IPv6Resrv::TYPE_NA, lease.addr_, + (lease.prefixlen_ ? lease.prefixlen_ : 128))); + } + + return (IPv6Resrv(IPv6Resrv::TYPE_PD, lease.addr_, lease.prefixlen_)); + } + private: /// @brief creates a lease and inserts it in LeaseMgr if necessary @@ -811,6 +852,9 @@ private: void allocateReservedLeases6(ClientContext6& ctx, Lease6Collection& existing_leases); + bool + allocateGlobalReservedLeases6(ClientContext6& ctx, Lease6Collection& existing_leases); + /// @brief Removes leases that are reserved for someone else. /// /// Goes through the list specified in existing_leases and removes those that diff --git a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc index 8394fd79be..da90675c33 100644 --- a/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc +++ b/src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc @@ -753,7 +753,7 @@ TEST_F(AllocEngine6Test, smallPool6) { // Create a subnet with a pool that has one address. initSubnet(IOAddress("2001:db8:1::"), addr, addr); - + // Initialize FQDN for a lease. initFqdn("myhost.example.com", true, true); @@ -2717,6 +2717,197 @@ TEST_F(SharedNetworkAlloc6Test, requestRunningOut) { EXPECT_TRUE(leases.empty()); } +// Verifies that client with a global hostname reservation can get and +// renew a dynamic lease from their selected subnet. +TEST_F(AllocEngine6Test, globalHostDynamicAddress) { + boost::scoped_ptr engine; + ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100))); + ASSERT_TRUE(engine); + + HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(), + Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL, + asiolink::IOAddress("0.0.0.0"))); + host->setHostname("ghost1"); + + CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); + CfgMgr::instance().commit(); + + subnet_->setHostReservationMode(Network::HR_GLOBAL); + + // Create context which will be used to try to allocate leases + Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234)); + AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query); + ctx.currentIA().iaid_ = iaid_; + + // Look up the reservation. + findReservation(*engine, ctx); + // Make sure we found our host. + ConstHostPtr current = ctx.currentHost(); + ASSERT_TRUE(current); + ASSERT_EQ("ghost1", current->getHostname()); + + // Check that we have been allocated a dynamic address. + Lease6Ptr lease; + ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx))); + ASSERT_TRUE(lease); + EXPECT_EQ("2001:db8:1::10", lease->addr_.toText()); + + // We're going to rollback the clock a little so we can verify a renewal. + // We verify the "time" change in case the lease returned to us + // by expectOneLease ever becomes a copy and not what is in the lease mgr. + --lease->cltt_; + Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_, + lease->addr_); + ASSERT_TRUE(from_mgr); + EXPECT_EQ(from_mgr->cltt_, lease->cltt_); + + // This is what the client will send in his renew message. + AllocEngine::HintContainer hints; + hints.push_back(make_pair(IOAddress("2001:db8:1::10"), 128)); + + // Set test fixture hostname_ to the expected value. This gets checked in + // renewTest. + hostname_ = "ghost1"; + + // Client should receive a lease. + Lease6Collection renewed = renewTest(*engine, pool_, hints, true); + ASSERT_EQ(1, renewed.size()); + + // And the lease lifetime should be extended. + EXPECT_GT(renewed[0]->cltt_, lease->cltt_) + << "Lease lifetime was not extended, but it should"; +} + +// Verifies that client with a global address reservation can get and +// renew a lease for an arbitrary address. +TEST_F(AllocEngine6Test, globalHostReservedAddress) { + boost::scoped_ptr engine; + ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100))); + ASSERT_TRUE(engine); + + HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(), + Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL, + asiolink::IOAddress("0.0.0.0"))); + host->setHostname("ghost1"); + IPv6Resrv resv(IPv6Resrv::TYPE_NA, asiolink::IOAddress("3001::1"), 128); + host->addReservation(resv); + + CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); + CfgMgr::instance().commit(); + + subnet_->setHostReservationMode(Network::HR_GLOBAL); + + // Create context which will be used to try to allocate leases + Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234)); + AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query); + ctx.currentIA().iaid_ = iaid_; + + // Look up the reservation. + findReservation(*engine, ctx); + // Make sure we found our host. + ConstHostPtr current = ctx.currentHost(); + ASSERT_TRUE(current); + ASSERT_EQ("ghost1", current->getHostname()); + + // Check that we have been allocated the fixed address. + Lease6Ptr lease; + ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx))); + ASSERT_TRUE(lease); + EXPECT_EQ("3001::1", lease->addr_.toText()); + + // We're going to rollback the clock a little so we can verify a renewal. + // We verify the "time" change in case the lease returned to us + // by expectOneLease ever becomes a copy and not what is in the lease mgr. + --lease->cltt_; + Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_, + lease->addr_); + ASSERT_TRUE(from_mgr); + EXPECT_EQ(from_mgr->cltt_, lease->cltt_); + + // This is what the client will send in his renew message. + AllocEngine::HintContainer hints; + hints.push_back(make_pair(IOAddress("3001::1"), 128)); + + // Set test fixture hostname_ to the expected value. This gets checked in + // renewTest. + hostname_ = "ghost1"; + + // Client should receive a lease. + Lease6Collection renewed = renewTest(*engine, pool_, hints, false); + ASSERT_EQ(1, renewed.size()); + + // And the lease lifetime should be extended. + EXPECT_GT(renewed[0]->cltt_, lease->cltt_) + << "Lease lifetime was not extended, but it should"; +} + +// Verifies that client with a global prefix reservation can get and +// renew a lease for an arbitrary prefix. +TEST_F(AllocEngine6Test, globalHostReservedPrefix) { + boost::scoped_ptr engine; + ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100))); + ASSERT_TRUE(engine); + + HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(), + Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL, + asiolink::IOAddress("0.0.0.0"))); + host->setHostname("ghost1"); + IPv6Resrv resv(IPv6Resrv::TYPE_PD, asiolink::IOAddress("3001::"), 64); + host->addReservation(resv); + + CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host); + CfgMgr::instance().commit(); + + subnet_->setHostReservationMode(Network::HR_GLOBAL); + + // Create context which will be used to try to allocate leases + Pkt6Ptr query(new Pkt6(DHCPV6_REQUEST, 1234)); + AllocEngine::ClientContext6 ctx(subnet_, duid_, false, false, "", false, query); + ctx.currentIA().type_ = Lease::TYPE_PD; + ctx.currentIA().iaid_ = iaid_; + + // Look up the reservation. + findReservation(*engine, ctx); + // Make sure we found our host. + ConstHostPtr current = ctx.currentHost(); + ASSERT_TRUE(current); + ASSERT_EQ("ghost1", current->getHostname()); + + // Check that we have been allocated the fixed address. + Lease6Ptr lease; + ASSERT_NO_THROW(lease = expectOneLease(engine->allocateLeases6(ctx))); + ASSERT_TRUE(lease); + EXPECT_EQ("3001::", lease->addr_.toText()); + + // We're going to rollback the clock a little so we can verify a renewal. + // We verify the "time" change in case the lease returned to us + // by expectOneLease ever becomes a copy and not what is in the lease mgr. + --lease->cltt_; + Lease6Ptr from_mgr = LeaseMgrFactory::instance().getLease6(lease->type_, + lease->addr_); + ASSERT_TRUE(from_mgr); + EXPECT_EQ(from_mgr->cltt_, lease->cltt_); + + // This is what the client will send in his renew message. + AllocEngine::HintContainer hints; + hints.push_back(make_pair(IOAddress("3001::"), 64)); + + // Set test fixture hostname_ to the expected value. This gets checked via + // renewTest. + hostname_ = "ghost1"; + + // We need a PD pool to fake renew_test + Pool6Ptr dummy_pool(new Pool6(Lease::TYPE_PD, IOAddress("2001:db8::"), 64, 64)); + + // Client should receive a lease. + Lease6Collection renewed = renewTest(*engine, dummy_pool, hints, false); + ASSERT_EQ(1, renewed.size()); + + // And the lease lifetime should be extended. + EXPECT_GT(renewed[0]->cltt_, lease->cltt_) + << "Lease lifetime was not extended, but it should"; +} + }; // namespace test }; // namespace dhcp }; // namespace isc diff --git a/src/lib/dhcpsrv/tests/alloc_engine_utils.cc b/src/lib/dhcpsrv/tests/alloc_engine_utils.cc index 8fa12fa838..67e389c00c 100644 --- a/src/lib/dhcpsrv/tests/alloc_engine_utils.cc +++ b/src/lib/dhcpsrv/tests/alloc_engine_utils.cc @@ -44,7 +44,7 @@ namespace test { bool testStatistics(const std::string& stat_name, const int64_t exp_value, const SubnetID subnet_id) { try { - std::string name = (!subnet_id ? stat_name : + std::string name = (subnet_id == SUBNET_ID_UNUSED ? stat_name : StatsMgr::generateName("subnet", subnet_id, stat_name)); ObservationPtr observation = StatsMgr::instance().getObservation(name); if (observation) { diff --git a/src/lib/dhcpsrv/tests/alloc_engine_utils.h b/src/lib/dhcpsrv/tests/alloc_engine_utils.h index 1d83d505cd..9a0e9b5b66 100644 --- a/src/lib/dhcpsrv/tests/alloc_engine_utils.h +++ b/src/lib/dhcpsrv/tests/alloc_engine_utils.h @@ -44,7 +44,7 @@ namespace test { /// @return true if the statistic manager holds a particular value, /// false otherwise. bool testStatistics(const std::string& stat_name, const int64_t exp_value, - const SubnetID subnet_id = 0); + const SubnetID subnet_id = SUBNET_ID_UNUSED); /// @brief Allocation engine with some internal methods exposed class NakedAllocEngine : public AllocEngine { @@ -178,9 +178,11 @@ public: EXPECT_EQ(lease->subnet_id_, subnet_->getID()); if (expected_in_subnet) { - EXPECT_TRUE(subnet_->inRange(lease->addr_)); + EXPECT_TRUE(subnet_->inRange(lease->addr_)) + << " address: " << lease->addr_.toText(); } else { - EXPECT_FALSE(subnet_->inRange(lease->addr_)); + EXPECT_FALSE(subnet_->inRange(lease->addr_)) + << " address: " << lease->addr_.toText(); } if (expected_in_pool) { @@ -374,7 +376,7 @@ public: createHost6(bool add_to_host_mgr, IPv6Resrv::Type type, const asiolink::IOAddress& addr, uint8_t prefix_len) { HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(), - Host::IDENT_DUID, SubnetID(0), subnet_->getID(), + Host::IDENT_DUID, SUBNET_ID_UNUSED, subnet_->getID(), asiolink::IOAddress("0.0.0.0"))); IPv6Resrv resv(type, addr, prefix_len); host->addReservation(resv);