]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#13,!6] allocation engine modifications to support v6 global host reservations
authorThomas Markwalder <tmark@isc.org>
Fri, 17 Aug 2018 18:27:57 +0000 (14:27 -0400)
committerThomas Markwalder <tmark@isc.org>
Fri, 17 Aug 2018 18:27:57 +0000 (14:27 -0400)
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

src/lib/dhcpsrv/alloc_engine.cc
src/lib/dhcpsrv/alloc_engine.h
src/lib/dhcpsrv/tests/alloc_engine6_unittest.cc
src/lib/dhcpsrv/tests/alloc_engine_utils.cc
src/lib/dhcpsrv/tests/alloc_engine_utils.h

index 90b9058de4f0fd0400938b48655940d65517cda8..39aaabf604df62bffaa884c6ad6203de621180db 100644 (file)
@@ -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<bool>(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<bool>(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<int>(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<int64_t>(-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<int64_t>(-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
index 198e32c9cd93787eb1b7c78f16190abd30dd98aa..b806af22855fa270c3aed4f0344d3c2261511c39 100644 (file)
@@ -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
index 8394fd79beb654841b228831374f5ccb4ec58575..da90675c334502b52c41d172b869eafb12f2e1fd 100644 (file)
@@ -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<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+                 Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
+                 asiolink::IOAddress("0.0.0.0")));
+    host->setHostname("ghost1");
+
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    subnet_->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<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+                 Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
+                 asiolink::IOAddress("0.0.0.0")));
+    host->setHostname("ghost1");
+    IPv6Resrv resv(IPv6Resrv::TYPE_NA, asiolink::IOAddress("3001::1"), 128);
+    host->addReservation(resv);
+
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    subnet_->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<AllocEngine> engine;
+    ASSERT_NO_THROW(engine.reset(new AllocEngine(AllocEngine::ALLOC_ITERATIVE, 100)));
+    ASSERT_TRUE(engine);
+
+    HostPtr host(new Host(&duid_->getDuid()[0], duid_->getDuid().size(),
+                 Host::IDENT_DUID, SUBNET_ID_UNUSED, SUBNET_ID_GLOBAL,
+                 asiolink::IOAddress("0.0.0.0")));
+    host->setHostname("ghost1");
+    IPv6Resrv resv(IPv6Resrv::TYPE_PD, asiolink::IOAddress("3001::"), 64);
+    host->addReservation(resv);
+
+    CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(host);
+    CfgMgr::instance().commit();
+
+    subnet_->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
index 8fa12fa838679ceaf255c40091229836420243cb..67e389c00c7c76399b2c8c248f8f9078b27a95b4 100644 (file)
@@ -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) {
index 1d83d505cd95c464c0a3cc7333a434c105da90cf..9a0e9b5b6681391f720390a9da5a405f079a1d63 100644 (file)
@@ -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);