Browse Source

[master] kea-dhcp4 and kea-dhcp6 now recalculate lease stats after reconfigure

    Merges in trac4294
Thomas Markwalder 8 years ago
parent
commit
0abdcf15f8

+ 26 - 15
src/lib/dhcpsrv/cfg_subnets4.cc

@@ -8,6 +8,7 @@
 #include <dhcp/iface_mgr.h>
 #include <dhcpsrv/cfg_subnets4.h>
 #include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/subnet_id.h>
 #include <dhcpsrv/addr_utilities.h>
 #include <asiolink/io_address.h>
@@ -232,18 +233,21 @@ CfgSubnets4::removeStatistics() {
     using namespace isc::stats;
 
     // For each v4 subnet currently configured, remove the statistic.
-    /// @todo: May move this to CfgSubnets4 class if there will be more
-    /// statistics here.
+    StatsMgr& stats_mgr = StatsMgr::instance();
     for (Subnet4Collection::const_iterator subnet4 = subnets_.begin();
          subnet4 != subnets_.end(); ++subnet4) {
+        SubnetID subnet_id = (*subnet4)->getID();
+        stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+                                             "total-addresses"));
 
-        StatsMgr::instance().del(StatsMgr::generateName("subnet",
-                                                        (*subnet4)->getID(),
-                                                        "total-addresses"));
+        stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+                                             "assigned-addresses"));
 
-        StatsMgr::instance().del(StatsMgr::generateName("subnet",
-                                                        (*subnet4)->getID(),
-                                                        "assigned-addresses"));
+        stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+                                             "declined-addresses"));
+
+        stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+                                             "declined-reclaimed-addresses"));
     }
 }
 
@@ -251,14 +255,21 @@ void
 CfgSubnets4::updateStatistics() {
     using namespace isc::stats;
 
-    /// @todo: May move this to CfgSubnets4 class if there will be more
-    /// statistics here.
-    for (Subnet4Collection::const_iterator subnet = subnets_.begin();
-         subnet != subnets_.end(); ++subnet) {
+    StatsMgr& stats_mgr = StatsMgr::instance();
+    for (Subnet4Collection::const_iterator subnet4 = subnets_.begin();
+         subnet4 != subnets_.end(); ++subnet4) {
+        SubnetID subnet_id = (*subnet4)->getID();
+
+        stats_mgr.setValue(StatsMgr::
+                           generateName("subnet", subnet_id, "total-addresses"),
+                                        static_cast<int64_t>
+                                        ((*subnet4)->getPoolCapacity(Lease::
+                                                                     TYPE_V4)));
+    }
 
-        StatsMgr::instance().setValue(
-            StatsMgr::generateName("subnet", (*subnet)->getID(), "total-addresses"),
-            static_cast<int64_t>((*subnet)->getPoolCapacity(Lease::TYPE_V4)));
+    // Only recount the stats if we have subnets.
+    if (subnets_.begin() != subnets_.end()) {
+            LeaseMgrFactory::instance().recountLeaseStats4();
     }
 }
 

+ 33 - 21
src/lib/dhcpsrv/cfg_subnets6.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -7,6 +7,7 @@
 #include <config.h>
 #include <dhcpsrv/cfg_subnets6.h>
 #include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/subnet_id.h>
 #include <stats/stats_mgr.h>
 
@@ -176,25 +177,26 @@ void
 CfgSubnets6::removeStatistics() {
     using namespace isc::stats;
 
+    StatsMgr& stats_mgr = StatsMgr::instance();
     // For each v6 subnet currently configured, remove the statistics.
     for (Subnet6Collection::const_iterator subnet6 = subnets_.begin();
          subnet6 != subnets_.end(); ++subnet6) {
+        SubnetID subnet_id = (*subnet6)->getID();
+        stats_mgr.del(StatsMgr::generateName("subnet", subnet_id, "total-nas"));
 
-        StatsMgr::instance().del(StatsMgr::generateName("subnet",
-                                                        (*subnet6)->getID(),
-                                                        "total-nas"));
+        stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+                                             "assigned-nas"));
 
-        StatsMgr::instance().del(StatsMgr::generateName("subnet",
-                                                        (*subnet6)->getID(),
-                                                        "assigned-nas"));
+        stats_mgr.del(StatsMgr::generateName("subnet", subnet_id, "total-pds"));
 
-        StatsMgr::instance().del(StatsMgr::generateName("subnet",
-                                                        (*subnet6)->getID(),
-                                                        "total-pds"));
+        stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+                                             "assigned-pds"));
 
-        StatsMgr::instance().del(StatsMgr::generateName("subnet",
-                                                        (*subnet6)->getID(),
-                                                        "assigned-pds"));
+        stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+                                             "declined-addresses"));
+
+        stats_mgr.del(StatsMgr::generateName("subnet", subnet_id,
+                                             "declined-reclaimed-addresses"));
     }
 }
 
@@ -202,16 +204,26 @@ void
 CfgSubnets6::updateStatistics() {
     using namespace isc::stats;
 
-    for (Subnet6Collection::const_iterator subnet = subnets_.begin();
-         subnet != subnets_.end(); ++subnet) {
+    StatsMgr& stats_mgr = StatsMgr::instance();
+    // For each v6 subnet currently configured, calculate totals
+    for (Subnet6Collection::const_iterator subnet6 = subnets_.begin();
+         subnet6 != subnets_.end(); ++subnet6) {
+        SubnetID subnet_id = (*subnet6)->getID();
+
+        stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id,
+                                                  "total-nas"),
+                           static_cast<int64_t>
+                           ((*subnet6)->getPoolCapacity(Lease::TYPE_NA)));
 
-        StatsMgr::instance().setValue(
-            StatsMgr::generateName("subnet", (*subnet)->getID(), "total-nas"),
-            static_cast<int64_t>((*subnet)->getPoolCapacity(Lease::TYPE_NA)));
+        stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id,
+                                                  "total-pds"),
+                            static_cast<int64_t>
+                            ((*subnet6)->getPoolCapacity(Lease::TYPE_PD)));
+    }
 
-        StatsMgr::instance().setValue(
-            StatsMgr::generateName("subnet", (*subnet)->getID(), "total-pds"),
-            static_cast<int64_t>((*subnet)->getPoolCapacity(Lease::TYPE_PD)));
+    // Only recount the stats if we have subnets.
+    if (subnets_.begin() != subnets_.end()) {
+            LeaseMgrFactory::instance().recountLeaseStats6();
     }
 }
 

+ 0 - 2
src/lib/dhcpsrv/lease.h

@@ -52,8 +52,6 @@ struct Lease {
     /// @brief Expired and reclaimed lease.
     static const uint32_t STATE_EXPIRED_RECLAIMED;
 
-    //@}
-
     /// @brief Returns name(s) of the basic lease state(s).
     ///
     /// @param state A numeric value holding a state information.

+ 163 - 0
src/lib/dhcpsrv/lease_mgr.cc

@@ -6,8 +6,11 @@
 
 #include <config.h>
 
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/lease_mgr.h>
 #include <exceptions/exceptions.h>
+#include <stats/stats_mgr.h>
 
 #include <boost/foreach.hpp>
 #include <boost/algorithm/string.hpp>
@@ -44,6 +47,166 @@ LeaseMgr::getLease6(Lease::Type type, const DUID& duid,
     return (*col.begin());
 }
 
+void
+LeaseMgr::recountLeaseStats4() {
+    using namespace stats;
+
+    StatsMgr& stats_mgr = StatsMgr::instance();
+
+    LeaseStatsQueryPtr query = startLeaseStatsQuery4();
+    if (!query) {
+        /// NULL means not backend does not support recounting.
+        return;
+    }
+
+    // Zero out the global stats.
+    int64_t zero = 0;
+    stats_mgr.setValue("declined-addresses", zero);
+    stats_mgr.setValue("declined-reclaimed-addresses", zero);
+
+    // Clear subnet level stats.  This ensures we don't end up with corner
+    // cases that leave stale values in place.
+    const Subnet4Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+
+    for (Subnet4Collection::const_iterator subnet = subnets->begin();
+         subnet != subnets->end(); ++subnet) {
+        SubnetID subnet_id = (*subnet)->getID();
+        stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id,
+                                                  "assigned-addresses"),
+                           zero);
+
+        stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id,
+                                                  "declined-addresses"),
+                           zero);
+        stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id,
+                                                  "declined-reclaimed-addresses"),
+                           zero);
+    }
+
+    // Get counts per state per subnet. Iterate over the result set
+    // updating the subnet and global values.
+    LeaseStatsRow row;
+    while (query->getNextRow(row)) {
+        if (row.lease_state_ == Lease::STATE_DEFAULT) {
+            // Set subnet level value.
+            stats_mgr.setValue(StatsMgr::generateName("subnet", row.subnet_id_,
+                                                      "assigned-addresses"),
+                               row.state_count_);
+        } else if (row.lease_state_ == Lease::STATE_DECLINED) {
+            // Set subnet level value.
+            stats_mgr.setValue(StatsMgr::generateName("subnet", row.subnet_id_,
+                                                      "declined-addresses"),
+                               row.state_count_);
+
+            // Add to the global value.
+            stats_mgr.addValue("declined-addresses", row.state_count_);
+        }
+    }
+}
+
+LeaseStatsQueryPtr
+LeaseMgr::startLeaseStatsQuery4() {
+    return(LeaseStatsQueryPtr());
+}
+
+bool
+LeaseStatsQuery::getNextRow(LeaseStatsRow& /*row*/) {
+    return (false);
+}
+
+void
+LeaseMgr::recountLeaseStats6() {
+    using namespace stats;
+
+    StatsMgr& stats_mgr = StatsMgr::instance();
+
+    LeaseStatsQueryPtr query = startLeaseStatsQuery6();
+    if (!query) {
+        /// NULL means not backend does not support recounting.
+        return;
+    }
+
+    // Zero out the global stats. (Ok, so currently there's only one
+    // that should be cleared.  "reclaimed-declined-addresses" never
+    // gets zeroed. @todo discuss with Tomek the rational of not
+    // clearing it when we clear the rest.
+    int64_t zero = 0;
+    stats_mgr.setValue("declined-addresses", zero);
+    stats_mgr.setValue("declined-reclaimed-addresses", zero);
+
+    // Clear subnet level stats.  This ensures we don't end up with corner
+    // cases that leave stale values in place.
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+
+    for (Subnet6Collection::const_iterator subnet = subnets->begin();
+         subnet != subnets->end(); ++subnet) {
+        SubnetID subnet_id = (*subnet)->getID();
+        stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id,
+                                                  "assigned-nas"),
+                           zero);
+
+        stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id,
+                                                  "declined-addresses"),
+                           zero);
+
+        stats_mgr.setValue(StatsMgr::
+                           generateName("subnet", subnet_id,
+                                        "declined-reclaimed-addresses"),
+                           zero);
+
+        stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id,
+                                                  "assigned-pds"),
+                           zero);
+    }
+
+    // Get counts per state per subnet. Iterate over the result set
+    // updating the subnet and global values.
+    LeaseStatsRow row;
+    while (query->getNextRow(row)) {
+        switch(row.lease_type_) {
+            case Lease::TYPE_NA:
+                if (row.lease_state_ == Lease::STATE_DEFAULT) {
+                    // Set subnet level value.
+                    stats_mgr.setValue(StatsMgr::
+                                       generateName("subnet", row.subnet_id_,
+                                                    "assigned-nas"),
+                                       row.state_count_);
+                } if (row.lease_state_ == Lease::STATE_DECLINED) {
+                    // Set subnet level value.
+                    stats_mgr.setValue(StatsMgr::
+                                       generateName("subnet", row.subnet_id_,
+                                                    "declined-addresses"),
+                                       row.state_count_);
+
+                    // Add to the global value.
+                    stats_mgr.addValue("declined-addresses", row.state_count_);
+                }
+                break;
+
+            case Lease::TYPE_PD:
+                if (row.lease_state_ == Lease::STATE_DEFAULT) {
+                    // Set subnet level value.
+                    stats_mgr.setValue(StatsMgr::
+                                       generateName("subnet", row.subnet_id_,
+                                                    "assigned-pds"),
+                                        row.state_count_);
+                }
+                break;
+
+            default:
+                // We dont' support TYPE_TAs yet
+                break;
+        }
+    }
+}
+
+LeaseStatsQueryPtr
+LeaseMgr::startLeaseStatsQuery6() {
+    return(LeaseStatsQueryPtr());
+}
+
 std::string
 LeaseMgr::getDBVersion() {
     isc_throw(NotImplemented, "LeaseMgr::getDBVersion() called");

+ 142 - 0
src/lib/dhcpsrv/lease_mgr.h

@@ -146,6 +146,86 @@ public:
     virtual ~SqlExchange() {};
     ExchangeColumnInfoContainer parameters_;   ///< Column names and types
 };
+
+/// @brief Contains a single row of lease statistical data
+///
+/// The contents of the row consist of a subnet ID, a lease
+/// type, a lease state, and the number of leases in that state
+/// for that type for that subnet ID.
+struct LeaseStatsRow {
+    /// @brief Default constructor
+    LeaseStatsRow() :
+        subnet_id_(0), lease_type_(Lease::TYPE_NA),
+        lease_state_(Lease::STATE_DEFAULT), state_count_(0) {
+    }
+
+    /// @brief Constructor
+    ///
+    /// Constructor which defaults the type to TYPE_NA.
+    ///
+    /// @param subnet_id The subnet id to which this data applies
+    /// @param lease_state The lease state counted
+    /// @param state_count The count of leases in the lease state
+    LeaseStatsRow(const SubnetID& subnet_id, const uint32_t lease_state,
+                  const int64_t state_count)
+        : subnet_id_(subnet_id), lease_type_(Lease::TYPE_NA),
+          lease_state_(lease_state), state_count_(state_count) {
+    }
+
+    /// @brief Constructor
+    ///
+    /// @param subnet_id The subnet id to which this data applies
+    /// @param lease_type The lease type for this state count
+    /// @param lease_state The lease state counted
+    /// @param state_count The count of leases in the lease state
+    LeaseStatsRow(const SubnetID& subnet_id, const Lease::Type& lease_type,
+                  const uint32_t lease_state, const int64_t state_count)
+        : subnet_id_(subnet_id), lease_type_(lease_type),
+          lease_state_(lease_state), state_count_(state_count) {
+    }
+
+    /// @brief The subnet ID to which this data applies
+    SubnetID subnet_id_;
+    /// @brief The lease_type to which the count applies
+    Lease::Type lease_type_;
+    /// @brief The lease_state to which the count applies
+    uint32_t lease_state_;
+    /// @brief state_count The count of leases in the lease state
+    int64_t state_count_;
+};
+
+/// @brief Base class for fulfilling a statistical lease data query
+///
+/// LeaseMgr derivations implement this class such that it provides
+/// upto date statistical lease data organized as rows of LeaseStatsRow
+/// instances. The rows must be accessible in ascending order by subnet id.
+class LeaseStatsQuery {
+public:
+    /// @brief Default constructor
+    LeaseStatsQuery() {};
+
+    /// @brief virtual destructor
+    virtual ~LeaseStatsQuery() {};
+
+    /// @brief Executes the query
+    ///
+    /// This method should conduct whatever steps are required to
+    /// calculate the lease statistical data by examining the
+    /// lease data and making that results available row by row.
+    virtual void start() {};
+
+    /// @brief Fetches the next row of data
+    ///
+    /// @param[out] row Storage into which the row is fetched
+    ///
+    /// @return True if a row was fetched, false if there are no
+    /// more rows.
+    virtual bool getNextRow(LeaseStatsRow& row);
+};
+
+/// @brief Defines a pointer to an LeaseStatsQuery.
+typedef boost::shared_ptr<LeaseStatsQuery> LeaseStatsQueryPtr;
+
 /// @brief Abstract Lease Manager
 ///
 /// This is an abstract API for lease database backends. It provides unified
@@ -397,6 +477,68 @@ public:
     /// @return Number of leases deleted.
     virtual uint64_t deleteExpiredReclaimedLeases6(const uint32_t secs) = 0;
 
+    /// @brief Recalculates per-subnet and global stats for IPv4 leases
+    ///
+    /// This method recalculates the following statistics:
+    /// per-subnet:
+    /// - assigned-addresses
+    /// - declined-addresses
+    /// - declined-reclaimed-addresses (reset to zero)
+    /// global:
+    /// - declined-addresses
+    /// - declined-reclaimed-addresses (reset to zero)
+    ///
+    /// It invokes the virtual method, startLeaseStatsQuery4(), which
+    /// returns an instance of an LeaseStatsQuery.  The query
+    /// query contains a "result set"  where each row is an LeaseStatRow
+    /// that contains a subnet id, a lease type (currently always TYPE_NA),
+    /// a lease state, and the number of leases of that type, in that state
+    /// and is ordered by subnet id.  The method iterates over the
+    /// result set rows, setting the appropriate statistic per subnet and
+    /// adding to the approporate global statistic.
+    void recountLeaseStats4();
+
+    /// @brief Virtual method which creates and runs the IPv4 lease stats query
+    ///
+    /// LeaseMgr derivations implement this method such that it creates and
+    /// returns an instance of an LeaseStatsQuery whose result set has been
+    /// populated with upto date IPv4 lease statistical data.  Each row of the
+    /// result set is an LeaseStatRow which ordered ascending by subnet ID.
+    ///
+    /// @return A populated LeaseStatsQuery
+    virtual LeaseStatsQueryPtr startLeaseStatsQuery4();
+
+    /// @brief Recalculates per-subnet and global stats for IPv6 leases
+    ///
+    /// This method recalculates the following statistics:
+    /// per-subnet:
+    /// - assigned-addresses
+    /// - declined-addresses
+    /// - declined-reclaimed-addresses (reset to zero)
+    /// - assigned-pds
+    /// global:
+    /// - declined-addresses
+    /// - declined-reclaimed-addresses (reset to zero)
+    ///
+    /// It invokes the virtual method, startLeaseStatsQuery6(), which
+    /// returns an instance of an LeaseStatsQuery.  The query contains
+    /// a "result set" where each row is an LeaseStatRow that contains
+    /// a subnet id, a lease type, a lease state, and the number of leases
+    /// of that type, in that state and is ordered by subnet id. The method
+    /// iterates over the result set rows, setting the appropriate statistic
+    /// per subnet and adding to the approporate global statistic.
+    void recountLeaseStats6();
+
+    /// @brief Virtual method which creates and runs the IPv6 lease stats query
+    ///
+    /// LeaseMgr derivations implement this method such that it creates and
+    /// returns an instance of an LeaseStatsQuery whose result set has been
+    /// populated with upto date IPv6 lease statistical data.  Each row of the
+    /// result set is an LeaseStatRow which ordered ascending by subnet ID.
+    ///
+    /// @return A populated LeaseStatsQuery
+    virtual LeaseStatsQueryPtr startLeaseStatsQuery6();
+
     /// @brief Return backend type
     ///
     /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)

+ 5 - 0
src/lib/dhcpsrv/lease_mgr_factory.cc

@@ -101,6 +101,11 @@ LeaseMgrFactory::destroy() {
     getLeaseMgrPtr().reset();
 }
 
+bool 
+LeaseMgrFactory::haveInstance() {
+    return (getLeaseMgrPtr().get());
+}
+
 LeaseMgr&
 LeaseMgrFactory::instance() {
     LeaseMgr* lmptr = getLeaseMgrPtr().get();

+ 4 - 1
src/lib/dhcpsrv/lease_mgr_factory.h

@@ -85,7 +85,10 @@ public:
     ///        create() to create one before calling this method.
     static LeaseMgr& instance();
 
-
+    /// @brief Indicates if the lease manager has been instantiated.
+    ///
+    /// @return True if the lease manager instance exists, false otherwise.
+    static bool haveInstance();
 
 private:
     /// @brief Hold pointer to lease manager

+ 277 - 0
src/lib/dhcpsrv/memfile_lease_mgr.cc

@@ -256,6 +256,268 @@ LFCSetup::getExitStatus() const {
     return (process_->getExitStatus(pid_));
 }
 
+
+/// @brief Base Memfile derivation of the statistical lease data query
+///
+/// This class provides the functionality such as results storage and row
+/// fetching common to fulfilling the statistical lease data query.
+///
+class MemfileLeaseStatsQuery : public LeaseStatsQuery {
+public:
+    /// @brief Constructor
+    ///
+    MemfileLeaseStatsQuery()
+    : rows_(0), next_pos_(rows_.end()) {
+    };
+
+    /// @brief Destructor
+    virtual ~MemfileLeaseStatsQuery() {};
+
+    /// @brief Fetches the next row in the result set
+    ///
+    /// Once the internal result set has been populated by invoking the
+    /// the start() method, this method is used to iterate over the
+    /// result set rows.  Once the last row has been fetched, subsequent
+    /// calls will return false.
+    /// @param row Storage for the fetched row
+    ///
+    /// @return True if the fetch succeeded, false if there are no more
+    /// rows to fetch.
+    virtual bool getNextRow(LeaseStatsRow& row) {
+        if (next_pos_ == rows_.end()) {
+            return (false);
+        }
+
+        row = *next_pos_;
+        ++next_pos_;
+        return (true);
+    }
+
+    /// @brief Returns the number of rows in the result set
+    int getRowCount() const {
+        return (rows_.size());
+    }
+
+protected:
+    /// @brief A vector containing the "result set"
+    std::vector<LeaseStatsRow> rows_;
+
+    /// @brief An iterator for accessing the next row within the result set
+    std::vector<LeaseStatsRow>::iterator next_pos_;
+};
+
+/// @brief Memfile derivation of the IPv4 statistical lease data query
+///
+/// This class is used to recalculate IPv4 lease statistics for Memfile
+/// lease storage.  It does so by iterating over the given storage,
+/// accumulating counts of leases in each of the monitored lease states
+/// for each subnet and storing these counts in an internal collection.
+/// The populated result set will contain one entry per monitored state
+/// per subnet.
+///
+class MemfileLeaseStatsQuery4 : public MemfileLeaseStatsQuery {
+public:
+    /// @brief Constructor
+    ///
+    /// @param storage4 A pointer to the v4 lease storage to be counted
+    MemfileLeaseStatsQuery4(Lease4Storage& storage4)
+    : MemfileLeaseStatsQuery(), storage4_(storage4) {
+    };
+
+    /// @brief Destructor
+    virtual ~MemfileLeaseStatsQuery4() {};
+
+    /// @brief Creates the IPv4 lease statistical data result set
+    ///
+    /// The result set is populated by iterating over the IPv4 leases in
+    /// storage, in ascending order by address, accumulating the lease state
+    /// counts per subnet. Note that walking the leases by address should
+    /// inherently group them by subnet, and while this does not gaurantee
+    /// ascending order of subnet id, it should be sufficient to accumulate
+    /// state counts per subnet.  This avoids introducing an additional
+    /// subnet_id index.
+    /// At the completion of all entries for a given subnet, the counts are
+    /// used to create LeaseStatsRow instances which are appended to an
+    /// internal vector.  The process results in a vector containing one entry
+    /// per state per subnet.
+    ///
+    /// Currently the states counted are:
+    ///
+    /// - Lease::STATE_DEFAULT (i.e. assigned)
+    /// - Lease::STATE_DECLINED
+    void start() {
+        const Lease4StorageAddressIndex& idx
+            = storage4_.get<AddressIndexTag>();
+
+        // Iterate over the leases in order by subnet, accumulating per
+        // subnet counts for each state of interest.  As we finish each
+        // subnet, add the appropriate rows to our result set.
+        SubnetID cur_id = 0;
+        int64_t assigned = 0;
+        int64_t declined = 0;
+        for(Lease4StorageAddressIndex::const_iterator lease = idx.begin();
+            lease != idx.end(); ++lease) {
+            // If we've hit the next subnet, add rows for the current subnet
+            // and wipe the accumulators
+            if ((*lease)->subnet_id_ != cur_id) {
+                if (cur_id > 0) {
+                    rows_.push_back(LeaseStatsRow(cur_id, Lease::STATE_DEFAULT,
+                                                  assigned));
+                    assigned = 0;
+                    rows_.push_back(LeaseStatsRow(cur_id, Lease::STATE_DECLINED,
+                                                  declined));
+                    declined = 0;
+                }
+
+                // Update current subnet id
+                cur_id = (*lease)->subnet_id_;
+            }
+
+            // Bump the appropriate accumulator
+            if ((*lease)->state_ == Lease::STATE_DEFAULT) {
+                ++assigned;
+            } else if ((*lease)->state_ == Lease::STATE_DECLINED) {
+                ++declined;
+            }
+        }
+
+        // Make the rows for last subnet, unless there were no rows
+        if (idx.begin() != idx.end()) {
+            rows_.push_back(LeaseStatsRow(cur_id, Lease::STATE_DEFAULT,
+                                          assigned));
+            rows_.push_back(LeaseStatsRow(cur_id, Lease::STATE_DECLINED,
+                                          declined));
+        }
+
+        // Set the next row position to the beginning of the rows.
+        next_pos_ = rows_.begin();
+    }
+
+private:
+    /// @brief The Memfile storage containing the IPv4 leases to analyze
+    Lease4Storage& storage4_;
+};
+
+
+/// @brief Memfile derivation of the IPv6 statistical lease data query
+///
+/// This class is used to recalculate IPv6 lease statistics for Memfile
+/// lease storage.  It does so by iterating over the given storage,
+/// accumulating counts of leases in each of the monitored lease states
+/// for each subnet and storing these counts in an internal collection.
+/// The populated result set will contain one entry per monitored state
+/// per subnet.
+///
+class MemfileLeaseStatsQuery6 : public MemfileLeaseStatsQuery {
+public:
+    /// @brief Constructor
+    ///
+    /// @param storage6 A pointer to the v6 lease storage to be counted
+    MemfileLeaseStatsQuery6(Lease6Storage& storage6)
+        : MemfileLeaseStatsQuery(), storage6_(storage6) {
+    };
+
+    /// @brief Destructor
+    virtual ~MemfileLeaseStatsQuery6() {};
+
+    /// @brief Creates the IPv6 lease statistical data result set
+    ///
+    /// The result set is populated by iterating over the IPv6 leases in
+    /// storage, in ascending order by address, accumulating the lease state
+    /// counts per subnet. Note that walking the leases by address should
+    /// inherently group them by subnet, and while this does not gaurantee
+    /// ascending order of subnet id, it should be sufficient to accumulate
+    /// state counts per subnet.  This avoids introducing an additional
+    /// subnet_id index.
+    /// At the completion of all entries for a given subnet, the counts
+    /// are used to create LeaseStatsRow instances which are appended to an
+    /// internal vector.  The process results in a vector containing one entry
+    /// per state per lease type per subnet.
+    ///
+    /// Currently the states counted are:
+    ///
+    /// - Lease::STATE_DEFAULT (i.e. assigned)
+    /// - Lease::STATE_DECLINED
+    virtual void start() {
+        // Get the subnet_id index
+        const Lease6StorageAddressIndex& idx
+            = storage6_.get<AddressIndexTag>();
+
+        // Iterate over the leases in order by subnet, accumulating per
+        // subnet counts for each state of interest.  As we finish each
+        // subnet, add the appropriate rows to our result set.
+        SubnetID cur_id = 0;
+        int64_t assigned = 0;
+        int64_t declined = 0;
+        int64_t assigned_pds = 0;
+
+        for(Lease6StorageAddressIndex::const_iterator lease = idx.begin();
+            lease != idx.end(); ++lease) {
+
+            // If we've hit the next subnet, add rows for the current subnet
+            // and wipe the accumulators
+            if ((*lease)->subnet_id_ != cur_id) {
+                if (cur_id > 0) {
+                    rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_NA,
+                                                  Lease::STATE_DEFAULT,
+                                                  assigned));
+                    assigned = 0;
+                    rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_NA,
+                                                  Lease::STATE_DECLINED,
+                                                  declined));
+                    declined = 0;
+                    rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_PD,
+                                                  Lease::STATE_DEFAULT,
+                                                  assigned_pds));
+                    assigned_pds = 0;
+                }
+
+                // Update current subnet id
+                cur_id = (*lease)->subnet_id_;
+            }
+
+            // Bump the appropriate accumulator
+            if ((*lease)->state_ == Lease::STATE_DEFAULT) {
+                switch((*lease)->type_) {
+                case Lease::TYPE_NA:
+                    ++assigned;
+                    break;
+                case Lease::TYPE_PD:
+                    ++assigned_pds;
+                    break;
+                default:
+                    break;
+                }
+            } else if ((*lease)->state_ == Lease::STATE_DECLINED) {
+                // In theory only NAs can be declined
+                if (((*lease)->type_) == Lease::TYPE_NA) {
+                    ++declined;
+                }
+            }
+        }
+
+        // Make the rows for last subnet, unless there were no rows
+        if (idx.begin() != idx.end()) {
+            rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_NA,
+                                          Lease::STATE_DEFAULT,
+                                          assigned));
+            rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_NA,
+                                          Lease::STATE_DECLINED,
+                                          declined));
+            rows_.push_back(LeaseStatsRow(cur_id, Lease::TYPE_PD,
+                                          Lease::STATE_DEFAULT,
+                                          assigned_pds));
+        }
+
+        // Set the next row position to the beginning of the rows.
+        next_pos_ = rows_.begin();
+    }
+
+private:
+    /// @brief The Memfile storage containing the IPv6 leases to analyze
+    Lease6Storage& storage6_;
+};
+
 // Explicit definition of class static constants.  Values are given in the
 // declaration so they're not needed here.
 const int Memfile_LeaseMgr::MAJOR_VERSION;
@@ -299,6 +561,7 @@ Memfile_LeaseMgr::Memfile_LeaseMgr(const DatabaseConnection::ParameterMap& param
         }
         lfcSetup(conversion_needed);
     }
+
 }
 
 Memfile_LeaseMgr::~Memfile_LeaseMgr() {
@@ -1048,5 +1311,19 @@ void Memfile_LeaseMgr::lfcExecute(boost::shared_ptr<LeaseFileType>& lease_file)
     }
 }
 
+LeaseStatsQueryPtr
+Memfile_LeaseMgr::startLeaseStatsQuery4() {
+    LeaseStatsQueryPtr query(new MemfileLeaseStatsQuery4(storage4_));
+    query->start();
+    return(query);
+}
+
+LeaseStatsQueryPtr
+Memfile_LeaseMgr::startLeaseStatsQuery6() {
+    LeaseStatsQueryPtr query(new MemfileLeaseStatsQuery6(storage6_));
+    query->start();
+    return(query);
+}
+
 } // end of namespace isc::dhcp
 } // end of namespace isc

+ 18 - 0
src/lib/dhcpsrv/memfile_lease_mgr.h

@@ -92,6 +92,7 @@ public:
 
     /// @}
 
+
     /// @brief Specifies universe (V4, V6)
     ///
     /// This enumeration is used by various functions in Memfile %Lease Manager,
@@ -594,6 +595,23 @@ public:
     int getLFCExitStatus() const;
     //@}
 
+    /// @brief Creates and runs the IPv4 lease stats query
+    ///
+    /// It creates an instance of a MemfileLeaseStatsQuery4 and then
+    /// invokes its start method in which the query constructs its
+    /// statistical data result set.  The query object is then returned.
+    ///
+    /// @return The populated query as a pointer to an LeaseStatsQuery
+    virtual LeaseStatsQueryPtr startLeaseStatsQuery4();
+
+    /// @brief Creates and runs the IPv6 lease stats query
+    ///
+    /// It creates an instance of a MemfileLeaseStatsQuery6 and then
+    /// invokes its start method in which the query constructs its
+    /// statistical data result set.  The query object is then returned.
+    ///
+    /// @return The populated query as a pointer to an LeaseStatsQuery.
+    virtual LeaseStatsQueryPtr startLeaseStatsQuery6();
 
     /// @name Protected methods used for %Lease File Cleanup.
     /// The following methods are protected so as they can be accessed and

+ 1 - 1
src/lib/dhcpsrv/memfile_lease_storage.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2015-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this

+ 163 - 1
src/lib/dhcpsrv/mysql_lease_mgr.cc

@@ -71,6 +71,7 @@ using namespace std;
 ///   lease object.
 
 namespace {
+    
 /// @brief Maximum length of the hostname stored in DNS.
 ///
 /// This length is restricted by the length of the domain-name carried
@@ -206,7 +207,14 @@ tagged_statements = { {
                         "prefix_len = ?, fqdn_fwd = ?, fqdn_rev = ?, "
                         "hostname = ?, hwaddr = ?, hwtype = ?, hwaddr_source = ?, "
                         "state = ? "
-                            "WHERE address = ?"}
+                            "WHERE address = ?"},
+    {MySqlLeaseMgr::RECOUNT_LEASE4_STATS,
+     "SELECT subnet_id, state, count(state) as state_count "
+     "  FROM lease4 GROUP BY subnet_id, state ORDER BY subnet_id"},
+    {MySqlLeaseMgr::RECOUNT_LEASE6_STATS,
+     "SELECT subnet_id, lease_type, state, count(state) as state_count"
+     "  FROM lease6 GROUP BY subnet_id, lease_type, state "
+     "  ORDER BY subnet_id" }
     }
 };
 
@@ -1215,8 +1223,145 @@ private:
     uint32_t        state_;             ///< Lease state.
 };
 
+/// @brief MySql derivation of the statistical lease data query
+///
+/// This class is used to recalculate lease statistics for MySQL
+/// lease storage.  It does so by executing a query which returns a result
+/// containining contain one row per monitored state per lease type per
+/// subnet, ordered by subnet id in ascending order.
+///
+class MySqlLeaseStatsQuery : public LeaseStatsQuery {
+public:
+    /// @brief Constructor
+    ///
+    /// @param conn A open connection to the database housing the lease data
+    /// @brief statement_index Index of the query's prepared statement
+    /// @brief fetch_type Indicates if query supplies lease type
+    MySqlLeaseStatsQuery(MySqlConnection& conn, const size_t statement_index,
+                         const bool fetch_type)
+        : conn_(conn), statement_index_(statement_index), statement_(NULL),
+         fetch_type_(fetch_type),
+         // Set the number of columns in the bind array based on fetch_type
+         // This is the number of columns expected in the result set
+         bind_(fetch_type_ ? 4 : 3) {
+        if (statement_index_ >= MySqlLeaseMgr::NUM_STATEMENTS) {
+            isc_throw(BadValue, "MySqlLeaseStatsQuery"
+                      " - invalid statement index" << statement_index_);
+        }
+
+        statement_ = conn.statements_[statement_index_];
+    }
+
+    /// @brief Destructor
+    virtual ~MySqlLeaseStatsQuery() {
+        (void) mysql_stmt_free_result(statement_);
+    }
 
+    /// @brief Creates the IPv4 lease statistical data result set
+    ///
+    /// The result set is populated by executing a SQL query against the
+    /// lease(4/6) table which sums the leases per lease state per lease
+    /// type (v6 only) per subnet id. This method binds the statement to
+    /// the output bind array and then executes the statement, and fetches
+    /// entire result set.
+    void start() {
+        int col = 0;
+        // subnet_id: unsigned int
+        bind_[col].buffer_type = MYSQL_TYPE_LONG;
+        bind_[col].buffer = reinterpret_cast<char*>(&subnet_id_);
+        bind_[col].is_unsigned = MLM_TRUE;
+        ++col;
+
+        // Fetch the lease type if we were told to do so.
+        if (fetch_type_) {
+            // lease type:  uint32_t
+            bind_[col].buffer_type = MYSQL_TYPE_LONG;
+            bind_[col].buffer = reinterpret_cast<char*>(&lease_type_);
+            bind_[col].is_unsigned = MLM_TRUE;
+            ++col;
+        } else {
+            fetch_type_ = Lease::TYPE_NA;
+        }
 
+        // state:  uint32_t
+        bind_[col].buffer_type = MYSQL_TYPE_LONG;
+        bind_[col].buffer = reinterpret_cast<char*>(&lease_state_);
+        bind_[col].is_unsigned = MLM_TRUE;
+        ++col;
+
+        // state_count_: uint32_t
+        bind_[col].buffer_type = MYSQL_TYPE_LONG;
+        bind_[col].buffer = reinterpret_cast<char*>(&state_count_);
+        bind_[col].is_unsigned = MLM_TRUE;
+
+        // Set up the MYSQL_BIND array for the data being returned
+        // and bind it to the statement.
+        int status = mysql_stmt_bind_result(statement_, &bind_[0]);
+        conn_.checkError(status, statement_index_, "outbound binding failed");
+
+        // Execute the statement
+        status = mysql_stmt_execute(statement_);
+        conn_.checkError(status, statement_index_, "unable to execute");
+
+        // Ensure that all the lease information is retrieved in one go to avoid
+        // overhead of going back and forth between client and server.
+        status = mysql_stmt_store_result(statement_);
+        conn_.checkError(status, statement_index_, "results storage failed");
+    }
+
+
+    /// @brief Fetches the next row in the result set
+    ///
+    /// Once the internal result set has been populated by invoking the
+    /// the start() method, this method is used to iterate over the
+    /// result set rows. Once the last row has been fetched, subsequent
+    /// calls will return false.
+    ///
+    /// @param row Storage for the fetched row
+    ///
+    /// @return True if the fetch succeeded, false if there are no more
+    /// rows to fetch.
+    bool getNextRow(LeaseStatsRow& row) {
+        bool have_row = false;
+        int status = mysql_stmt_fetch(statement_);
+        if (status == MLM_MYSQL_FETCH_SUCCESS) {
+            row.subnet_id_ = static_cast<SubnetID>(subnet_id_);
+            row.lease_type_ = static_cast<Lease::Type>(lease_type_);
+            row.lease_state_ = lease_state_;
+            row.state_count_ = state_count_;
+            have_row = true;
+        } else if (status != MYSQL_NO_DATA) {
+            conn_.checkError(status, statement_index_, "getNextRow failed");
+        }
+
+        return (have_row);
+    }
+
+private:
+    /// @brief Database connection to use to execute the query
+    MySqlConnection& conn_;
+
+    /// @brief Index of the query's prepared statement
+    size_t statement_index_;
+
+    /// @brief The query's prepared statement
+    MYSQL_STMT *statement_;
+
+    /// @brief Indicates if query supplies lease type
+    bool fetch_type_;
+
+    /// @brief Bind array used to store the query result set;
+    std::vector<MYSQL_BIND> bind_;
+
+    /// @brief Receives subnet ID when fetching a row
+    uint32_t subnet_id_;
+    /// @brief Receives the lease type when fetching a row
+    uint32_t lease_type_;
+    /// @brief Receives the lease state when fetching a row
+    uint32_t lease_state_;
+    /// @brief Receives the state count when fetching a row
+    uint32_t state_count_;
+};
 
 // MySqlLeaseMgr Constructor and Destructor
 
@@ -2026,6 +2171,23 @@ MySqlLeaseMgr::getVersion() const {
     return (std::make_pair(major, minor));
 }
 
+LeaseStatsQueryPtr
+MySqlLeaseMgr::startLeaseStatsQuery4() {
+    LeaseStatsQueryPtr query(new MySqlLeaseStatsQuery(conn_,
+                                                      RECOUNT_LEASE4_STATS,
+                                                      false));
+    query->start();
+    return(query);
+}
+
+LeaseStatsQueryPtr
+MySqlLeaseMgr::startLeaseStatsQuery6() {
+    LeaseStatsQueryPtr query(new MySqlLeaseStatsQuery(conn_,
+                                                      RECOUNT_LEASE6_STATS,
+                                                      true));
+    query->start();
+    return(query);
+}
 
 void
 MySqlLeaseMgr::commit() {

+ 21 - 1
src/lib/dhcpsrv/mysql_lease_mgr.h

@@ -25,7 +25,6 @@ namespace dhcp {
 class MySqlLease4Exchange;
 class MySqlLease6Exchange;
 
-
 /// @brief MySQL Lease Manager
 ///
 /// This class provides the \ref isc::dhcp::LeaseMgr interface to the MySQL
@@ -410,6 +409,8 @@ public:
         INSERT_LEASE6,               // Add entry to lease6 table
         UPDATE_LEASE4,               // Update a Lease4 entry
         UPDATE_LEASE6,               // Update a Lease6 entry
+        RECOUNT_LEASE4_STATS,        // Fetches IPv4 address statisics
+        RECOUNT_LEASE6_STATS,        // Fetches IPv6 address statisics
         NUM_STATEMENTS               // Number of statements
     };
 
@@ -590,6 +591,25 @@ private:
     uint64_t deleteExpiredReclaimedLeasesCommon(const uint32_t secs,
                                                 StatementIndex statement_index);
 
+    /// @brief Creates and runs the IPv4 lease stats query
+    ///
+    /// It creates an instance of a MySqlLeaseStatsQuery4 and then
+    /// invokes its start method, which fetches its statistical data
+    /// result set by executing the RECOUNT_LEASE_STATS4 query.
+    /// The query object is then returned.
+    ///
+    /// @return The populated query as a pointer to an LeaseStatsQuery
+    virtual LeaseStatsQueryPtr startLeaseStatsQuery4();
+
+    /// @brief Creates and runs the IPv6 lease stats query
+    ///
+    /// It creates an instance of a MySqlLeaseStatsQuery6 and then
+    /// invokes its start method, which fetches its statistical data
+    /// result set by executing the RECOUNT_LEASE_STATS6 query.
+    /// The query object is then returned.
+    ///
+    /// @return The populated query as a pointer to an LeaseStatsQuery
+    virtual LeaseStatsQueryPtr startLeaseStatsQuery6();
 
     /// @brief Check Error and Throw Exception
     ///

+ 135 - 1
src/lib/dhcpsrv/pgsql_lease_mgr.cc

@@ -26,7 +26,7 @@ using namespace std;
 
 namespace {
 
-/// @todo TKM lease6 needs to accomodate hwaddr,hwtype, and hwaddr source 
+/// @todo TKM lease6 needs to accomodate hwaddr,hwtype, and hwaddr source
 /// columns.  This is coverd by tickets #3557, #4530, and PR#9.
 
 /// @brief Catalog of all the SQL statements currently supported.  Note
@@ -201,6 +201,19 @@ PgSqlTaggedStatement tagged_statements[] = {
         "state = $13 "
       "WHERE address = $14"},
 
+    // RECOUNT_LEASE4_STATS,
+    { 0, { OID_NONE },
+      "recount_lease4_stats",
+      "SELECT subnet_id, state, count(state) as state_count "
+      "FROM lease4 GROUP BY subnet_id, state ORDER BY subnet_id"},
+
+    // RECOUNT_LEASE6_STATS,
+    { 0, { OID_NONE },
+      "recount_lease6_stats",
+      "SELECT subnet_id, lease_type, state, count(state) as state_count "
+      "FROM lease6 GROUP BY subnet_id, lease_type, state "
+      "ORDER BY subnet_id"},
+
     // End of list sentinel
     { 0,  { 0 }, NULL, NULL}
 };
@@ -681,6 +694,107 @@ private:
     //@}
 };
 
+/// @brief Base PgSql derivation of the statistical lease data query
+///
+/// This class provides the functionality such as results storgae and row
+/// fetching common to fulfilling the statistical lease data query.
+///
+class PgSqlLeaseStatsQuery : public LeaseStatsQuery {
+public:
+    /// @brief Constructor
+    ///
+    /// @param conn A open connection to the database housing the lease data
+    /// @param statement The lease data SQL prepared statement to execute
+    /// @param fetch_statement Indicates whether or not lease_type should be
+    /// fetched from the result set
+    PgSqlLeaseStatsQuery(PgSqlConnection& conn, PgSqlTaggedStatement& statement,
+                         const bool fetch_type)
+        : conn_(conn), statement_(statement), result_set_(), next_row_(0),
+         fetch_type_(fetch_type) {
+    }
+
+    /// @brief Destructor
+    virtual ~PgSqlLeaseStatsQuery() {};
+
+    /// @brief Creates the lease statistical data result set
+    ///
+    /// The result set is populated by executing a  prepared SQL query
+    /// against the database which sums the leases per lease state per
+    /// subnet id.
+    void start() {
+        // The query has no parameters, so we only need it's name.
+        result_set_.reset(new PgSqlResult(PQexecPrepared(conn_, statement_.name,
+                                          0, NULL, NULL, NULL, 0)));
+
+        conn_.checkStatementError(*result_set_, statement_);
+    }
+
+    /// @brief Fetches the next row in the result set
+    ///
+    /// Once the internal result set has been populated by invoking the
+    /// the start() method, this method is used to iterate over the
+    /// result set rows. Once the last row has been fetched, subsequent
+    /// calls will return false.
+    ///
+    /// @param row Storage for the fetched row
+    ///
+    /// @return True if the fetch succeeded, false if there are no more
+    /// rows to fetch.
+    bool getNextRow(LeaseStatsRow& row) {
+        // If we're past the end, punt.
+        if (next_row_ >= result_set_->getRows()) {
+            return (false);
+        }
+
+        // Fetch the subnet id.
+        uint32_t col = 0;
+        uint32_t subnet_id;
+        PgSqlExchange::getColumnValue(*result_set_, next_row_, col, subnet_id);
+        row.subnet_id_ = static_cast<SubnetID>(subnet_id);
+        ++col;
+
+        // Fetch the lease type if we were told to do so.
+        if (fetch_type_) {
+            uint32_t lease_type;
+            PgSqlExchange::getColumnValue(*result_set_, next_row_ , col,
+                                          lease_type);
+            row.lease_type_ = static_cast<Lease::Type>(lease_type);
+            ++col;
+        } else {
+            row.lease_type_ = Lease::TYPE_NA;
+        }
+
+        // Fetch the lease state.
+        PgSqlExchange::getColumnValue(*result_set_, next_row_ , col,
+                                      row.lease_state_);
+        ++col;
+
+        // Fetch the state count.
+        PgSqlExchange::getColumnValue(*result_set_, next_row_, col,
+                                      row.state_count_);
+
+        // Point to the next row.
+        ++next_row_;
+        return (true);
+    }
+
+protected:
+    /// @brief Database connection to use to execute the query
+    PgSqlConnection& conn_;
+
+    /// @brief The query's prepared statement
+    PgSqlTaggedStatement& statement_;
+
+    /// @brief The result set returned by Postgres.
+    boost::shared_ptr<PgSqlResult> result_set_;
+
+    /// @brief Index of the next row to fetch
+    uint32_t next_row_;
+
+    /// @brief Indicates if query supplies lease type
+    bool fetch_type_;
+};
+
 PgSqlLeaseMgr::PgSqlLeaseMgr(const DatabaseConnection::ParameterMap& parameters)
     : LeaseMgr(), exchange4_(new PgSqlLease4Exchange()),
     exchange6_(new PgSqlLease6Exchange()), conn_(parameters) {
@@ -1222,6 +1336,26 @@ PgSqlLeaseMgr::deleteExpiredReclaimedLeasesCommon(const uint32_t secs,
     return (deleteLeaseCommon(statement_index, bind_array));
 }
 
+LeaseStatsQueryPtr
+PgSqlLeaseMgr::startLeaseStatsQuery4() {
+    LeaseStatsQueryPtr query(
+        new PgSqlLeaseStatsQuery(conn_,
+                                 tagged_statements[RECOUNT_LEASE4_STATS],
+                                 false));
+    query->start();
+    return(query);
+}
+
+LeaseStatsQueryPtr
+PgSqlLeaseMgr::startLeaseStatsQuery6() {
+    LeaseStatsQueryPtr query(
+        new PgSqlLeaseStatsQuery(conn_,
+                                 tagged_statements[RECOUNT_LEASE6_STATS],
+                                 true));
+    query->start();
+    return(query);
+}
+
 string
 PgSqlLeaseMgr::getName() const {
     string name = "";

+ 22 - 0
src/lib/dhcpsrv/pgsql_lease_mgr.h

@@ -317,6 +317,26 @@ public:
     /// @return Number of leases deleted.
     virtual uint64_t deleteExpiredReclaimedLeases6(const uint32_t secs);
 
+    /// @brief Creates and runs the IPv4 lease stats query
+    ///
+    /// It creates an instance of a PgSqlLeaseStatsQuery4 and then
+    /// invokes its start method, which fetches its statistical data
+    /// result set by executing the RECOUNT_LEASE_STATS4 query.
+    /// The query object is then returned.
+    /// 
+    /// @return The populated query as a pointer to an LeaseStatsQuery
+    virtual LeaseStatsQueryPtr startLeaseStatsQuery4();
+
+    /// @brief Creates and runs the IPv6 lease stats query
+    ///
+    /// It creates an instance of a PgSqlLeaseStatsQuery and then
+    /// invokes its start method, which fetches its statistical data
+    /// result set by executing the RECOUNT_LEASE_STATS6 query.
+    /// The query object is then returned.
+    /// 
+    /// @return The populated query as a pointer to an LeaseStatsQuery
+    virtual LeaseStatsQueryPtr startLeaseStatsQuery6();
+
     /// @brief Return backend type
     ///
     /// Returns the type of the backend (e.g. "mysql", "memfile" etc.)
@@ -385,6 +405,8 @@ public:
         INSERT_LEASE6,              // Add entry to lease6 table
         UPDATE_LEASE4,              // Update a Lease4 entry
         UPDATE_LEASE6,              // Update a Lease6 entry
+        RECOUNT_LEASE4_STATS,       // Fetch IPv4 lease statistical data
+        RECOUNT_LEASE6_STATS,       // Fetch IPv4 lease statistical data
         NUM_STATEMENTS              // Number of statements
     };
 

+ 12 - 5
src/lib/dhcpsrv/srv_config.cc

@@ -7,6 +7,7 @@
 #include <config.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/srv_config.h>
+#include <dhcpsrv/lease_mgr_factory.h>
 #include <log/logger_manager.h>
 #include <log/logger_specification.h>
 #include <dhcp/pkt.h> // Needed for HWADDR_SOURCE_*
@@ -164,11 +165,17 @@ SrvConfig::removeStatistics() {
 
 void
 SrvConfig::updateStatistics() {
-
-    // Updates  statistics for v4 and v6 subnets
-    getCfgSubnets4()->updateStatistics();
-
-    getCfgSubnets6()->updateStatistics();
+    // Updating subnet statistics involves updating lease statistics, which
+    // is done by the LeaseMgr.  Since servers with subnets, must have a
+    // LeaseMgr, we do not bother updating subnet stats for servers without
+    // a lease manager, such as D2. @todo We should probably examine why
+    // "SrvConfig" is being used by D2.
+    if (LeaseMgrFactory::haveInstance()) {
+        // Updates  statistics for v4 and v6 subnets
+        getCfgSubnets4()->updateStatistics();
+
+        getCfgSubnets6()->updateStatistics();
+    }
 }
 
 }

+ 7 - 2
src/lib/dhcpsrv/tests/alloc_engine_utils.cc

@@ -123,6 +123,9 @@ AllocEngine4Test::generateDeclinedLease(const std::string& addr,
 AllocEngine6Test::AllocEngine6Test() {
     CfgMgr::instance().clear();
 
+    // This lease mgr needs to exist to before configuration commits.
+    factory_.create("type=memfile universe=6 persist=false");
+
     duid_ = DuidPtr(new DUID(std::vector<uint8_t>(8, 0x42)));
     iaid_ = 42;
 
@@ -141,7 +144,6 @@ AllocEngine6Test::AllocEngine6Test() {
 
     initFqdn("", false, false);
 
-    factory_.create("type=memfile universe=6 persist=false");
 }
 
 void
@@ -525,6 +527,10 @@ AllocEngine4Test::initSubnet(const asiolink::IOAddress& pool_start,
 }
 
 AllocEngine4Test::AllocEngine4Test() {
+
+    // This lease mgr needs to exist to before configuration commits.
+    factory_.create("type=memfile universe=4 persist=false");
+
     // Create fresh instance of the HostMgr, and drop any previous HostMgr state.
     HostMgr::instance().create();
 
@@ -548,7 +554,6 @@ AllocEngine4Test::AllocEngine4Test() {
     initSubnet(IOAddress("192.0.2.100"), IOAddress("192.0.2.109"));
     cfg_mgr.commit();
 
-    factory_.create("type=memfile universe=4 persist=false");
 
     // Create a default context. Note that remaining parameters must be
     // assigned when needed.

+ 1 - 0
src/lib/dhcpsrv/tests/cfg_db_access_unittest.cc

@@ -89,6 +89,7 @@ public:
     /// @brief Destructor.
     virtual ~CfgMySQLDbAccessTest() {
         destroyMySQLSchema();
+        LeaseMgrFactory::destroy();
     }
 };
 

+ 20 - 0
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc

@@ -10,6 +10,7 @@
 #include <dhcp/dhcp6.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/lease_mgr_factory.h>
 #include <dhcpsrv/subnet_id.h>
 #include <dhcpsrv/parsers/dhcp_parsers.h>
 #include <stats/stats_mgr.h>
@@ -280,6 +281,23 @@ public:
     void clear() {
         CfgMgr::instance().setVerbose(false);
         CfgMgr::instance().clear();
+        LeaseMgrFactory::destroy();
+    }
+
+    /// @brief Creates instance of the backend.
+    ///
+    /// @param family AF_INET for v4, AF_INET6 for v6
+    void startBackend(int family = AF_INET) {
+        try {
+            std::ostringstream s;
+            s << "type=memfile persist=false " << (family == AF_INET6 ?
+                                     "universe=6" : "universe=4");
+            LeaseMgrFactory::create(s.str());
+        } catch (const std::exception& ex) {
+            std::cerr << "*** ERROR: unable to create instance of the Memfile\n"
+                " lease database backend: " << ex.what() << std::endl;
+            throw;
+        }
     }
 
     /// used in client classification (or just empty container for other tests)
@@ -575,6 +593,7 @@ TEST_F(CfgMgrTest, verbosity) {
 TEST_F(CfgMgrTest, commitStats4) {
     CfgMgr& cfg_mgr = CfgMgr::instance();
     StatsMgr& stats_mgr = StatsMgr::instance();
+    startBackend(AF_INET);
 
     // Let's prepare the "old" configuration: a subnet with id 123
     // and pretend there were addresses assigned, so statistics are non-zero.
@@ -641,6 +660,7 @@ TEST_F(CfgMgrTest, clearStats4) {
 TEST_F(CfgMgrTest, commitStats6) {
     CfgMgr& cfg_mgr = CfgMgr::instance();
     StatsMgr& stats_mgr = StatsMgr::instance();
+    startBackend(AF_INET6);
 
     // Let's prepare the "old" configuration: a subnet with id 123
     // and pretend there were addresses assigned, so statistics are non-zero.

+ 304 - 2
src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc

@@ -5,11 +5,18 @@
 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 #include <config.h>
+
+#include <asiolink/io_address.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/database_connection.h>
 #include <dhcpsrv/tests/generic_lease_mgr_unittest.h>
 #include <dhcpsrv/tests/test_utils.h>
-#include <dhcpsrv/database_connection.h>
-#include <asiolink/io_address.h>
+#include <stats/stats_mgr.h>
+
+#include <boost/foreach.hpp>
+
 #include <gtest/gtest.h>
+
 #include <sstream>
 
 using namespace std;
@@ -57,6 +64,7 @@ GenericLeaseMgrTest::GenericLeaseMgrTest()
         /// a template
         leasetype6_.push_back(LEASETYPE6[i]);
     }
+
 }
 
 GenericLeaseMgrTest::~GenericLeaseMgrTest() {
@@ -2381,6 +2389,300 @@ GenericLeaseMgrTest::testGetDeclinedLeases6() {
     }
 }
 
+void
+GenericLeaseMgrTest::checkStat(const std::string& name,
+                               const int64_t expected_value) {
+    stats::ObservationPtr obs =
+        stats::StatsMgr::instance().getObservation(name);
+
+    ASSERT_TRUE(obs) << " stat: " << name << " not found ";
+    ASSERT_EQ(expected_value, obs->getInteger().first)
+                << " stat: " << name << " value wrong";
+}
+
+void
+GenericLeaseMgrTest::checkLeaseStats(const StatValMapList& expectedStats) {
+    // Global accumulators
+    int64_t declined_addresses = 0;
+    int64_t declined_reclaimed_addresses = 0;
+
+    // Iterate over all stats for each subnet
+    for (int subnet_idx = 0; subnet_idx < expectedStats.size(); ++subnet_idx) {
+        BOOST_FOREACH(StatValPair expectedStat, expectedStats[subnet_idx]) {
+            // Verify the per subnet value.
+            checkStat(stats::StatsMgr::generateName("subnet", subnet_idx+1,
+                                                    expectedStat.first),
+                      expectedStat.second);
+
+            // Add the value to globals as needed.
+            if (expectedStat.first == "declined-addresses") {
+                declined_addresses += expectedStat.second;
+            } else if (expectedStat.first == "declined-reclaimed-addresses") {
+                declined_reclaimed_addresses += expectedStat.second;
+            }
+        }
+    }
+
+    // Verify the globals.
+    checkStat("declined-addresses", declined_addresses);
+    checkStat("declined-reclaimed-addresses", declined_reclaimed_addresses);
+}
+
+void
+GenericLeaseMgrTest::makeLease4(const std::string& address,
+                                const SubnetID& subnet_id,
+                                const uint32_t state) {
+    Lease4Ptr lease(new Lease4());
+
+    // set the address
+    lease->addr_ = IOAddress(address);
+
+    // make a MAC from the address
+    std::vector<uint8_t> hwaddr = lease->addr_.toBytes();
+    hwaddr.push_back(0);
+    hwaddr.push_back(0);
+
+    lease->hwaddr_.reset(new HWAddr(hwaddr, HTYPE_ETHER));
+    lease->valid_lft_ = 86400;
+    lease->cltt_ = 168256;
+    lease->subnet_id_ = subnet_id;
+    lease->state_ = state;
+    ASSERT_TRUE(lmptr_->addLease(lease));
+}
+
+void
+GenericLeaseMgrTest::makeLease6(const Lease::Type& type,
+                                const std::string& address,
+                                uint8_t prefix_len,
+                                const SubnetID& subnet_id,
+                                const uint32_t state) {
+    IOAddress addr(address);
+
+    // make a DUID from the address
+    std::vector<uint8_t> bytes = addr.toBytes();
+    bytes.push_back(prefix_len);
+
+    Lease6Ptr lease(new Lease6(type, addr, DuidPtr(new DUID(bytes)), 77,
+                               16000, 24000, 0, 0, subnet_id, HWAddrPtr(),
+                               prefix_len));
+    lease->state_ = state;
+    ASSERT_TRUE(lmptr_->addLease(lease));
+}
+
+void
+GenericLeaseMgrTest::testRecountLeaseStats4() {
+    using namespace stats;
+
+    StatsMgr::instance().removeAll();
+
+    // Create two subnets.
+    int num_subnets = 2;
+    CfgSubnets4Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets4();
+    Subnet4Ptr subnet;
+    Pool4Ptr pool;
+
+    subnet.reset(new Subnet4(IOAddress("192.0.1.0"), 24, 1, 2, 3, 1));
+    pool.reset(new Pool4(IOAddress("192.0.1.0"), 24));
+    subnet->addPool(pool);
+    cfg->add(subnet);
+
+    subnet.reset(new Subnet4(IOAddress("192.0.2.0"), 24, 1, 2, 3, 2));
+    pool.reset(new Pool4(IOAddress("192.0.2.0"), 24));
+    subnet->addPool(pool);
+    cfg->add(subnet);
+
+
+    ASSERT_NO_THROW(CfgMgr::instance().commit());
+
+    // Create the expected stats list.  At this point, the only stat
+    // that should be non-zero is total-addresses.
+    StatValMapList expectedStats(num_subnets);
+    for (int i = 0; i < num_subnets; ++i) {
+        expectedStats[i]["total-addresses"] = 256;
+        expectedStats[i]["assigned-addresses"] = 0;
+        expectedStats[i]["declined-addresses"] = 0;
+        expectedStats[i]["declined-reclaimed-addresses"] = 0;
+    }
+
+    // Make sure stats are as expected.
+    ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats));
+
+    // Recount stats.  We should have the same results.
+    ASSERT_NO_THROW(lmptr_->recountLeaseStats4());
+
+    // Make sure stats are as expected.
+    ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats));
+
+    // Now let's insert some leases into subnet 1.
+    int subnet_id = 1;
+
+    // Insert one lease in default state, i.e. assigned.
+    makeLease4("192.0.1.1", subnet_id);
+
+    // Insert one lease in declined state.
+    makeLease4("192.0.1.2", subnet_id, Lease::STATE_DECLINED);
+
+    // Insert one lease in the expired state.
+    makeLease4("192.0.1.3", subnet_id, Lease::STATE_EXPIRED_RECLAIMED);
+
+    // Insert another lease in default state, i.e. assigned.
+    makeLease4("192.0.1.4", subnet_id);
+
+    // Update the expected stats list for subnet 1.
+    expectedStats[subnet_id - 1]["assigned-addresses"] = 2;
+    expectedStats[subnet_id - 1]["declined-addresses"] = 1;
+
+    // Now let's add leases to subnet 2.
+    subnet_id = 2;
+
+    // Insert one delined lease.
+    makeLease4("192.0.2.2", subnet_id, Lease::STATE_DECLINED);
+
+    // Update the expected stats.
+    expectedStats[subnet_id - 1]["declined-addresses"] = 1;
+
+    // Now Recount the stats.
+    ASSERT_NO_THROW(lmptr_->recountLeaseStats4());
+
+    // Make sure stats are as expected.
+    ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats));
+
+    // Delete some leases from subnet, and update the expected stats.
+    EXPECT_TRUE(lmptr_->deleteLease(IOAddress("192.0.1.1")));
+    expectedStats[0]["assigned-addresses"] = 1;
+
+    EXPECT_TRUE(lmptr_->deleteLease(IOAddress("192.0.1.2")));
+    expectedStats[0]["declined-addresses"] = 0;
+
+    // Recount the stats.
+    ASSERT_NO_THROW(lmptr_->recountLeaseStats4());
+
+    // Make sure stats are as expected.
+    ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats));
+}
+
+
+void
+GenericLeaseMgrTest::testRecountLeaseStats6() {
+    using namespace stats;
+
+    StatsMgr::instance().removeAll();
+
+    // Create two subnets.
+    int num_subnets = 2;
+    CfgSubnets6Ptr cfg = CfgMgr::instance().getStagingCfg()->getCfgSubnets6();
+    Subnet6Ptr subnet;
+    Pool6Ptr pool;
+    StatValMapList expectedStats(num_subnets);
+
+    int subnet_id = 1;
+    subnet.reset(new Subnet6(IOAddress("3001:1::"), 64, 1, 2, 3, 4, subnet_id));
+    pool.reset(new Pool6(Lease::TYPE_NA, IOAddress("3001:1::"),
+                         IOAddress("3001:1::FF")));
+    subnet->addPool(pool);
+    expectedStats[subnet_id - 1]["total-nas"] = 256;
+
+    pool.reset(new Pool6(Lease::TYPE_PD, IOAddress("3001:1:2::"),96,112));
+    subnet->addPool(pool);
+    expectedStats[subnet_id - 1]["total-pds"] = 65536;
+    cfg->add(subnet);
+
+    ++subnet_id;
+    subnet.reset(new Subnet6(IOAddress("2001:db8:1::"), 64, 1, 2, 3, 4,
+                             subnet_id));
+    pool.reset(new Pool6(Lease::TYPE_NA, IOAddress("2001:db8:1::"), 120));
+    subnet->addPool(pool);
+    expectedStats[subnet_id - 1]["total-nas"] = 256;
+    expectedStats[subnet_id - 1]["total-pds"] = 0;
+    cfg->add(subnet);
+
+    ASSERT_NO_THROW(CfgMgr::instance().commit());
+
+
+    // Create the expected stats list.  At this point, the only stat
+    // that should be non-zero is total-nas/total-pds.
+    for (int i = 0; i < num_subnets; ++i) {
+        expectedStats[i]["assigned-nas"] = 0;
+        expectedStats[i]["declined-addresses"] = 0;
+        expectedStats[i]["declined-reclaimed-addresses"] = 0;
+        expectedStats[i]["assigned-pds"] = 0;
+    }
+
+    // Make sure stats are as expected.
+    ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats));
+
+
+    // Recount stats.  We should have the same results.
+    ASSERT_NO_THROW(lmptr_->recountLeaseStats4());
+
+    // Make sure stats are as expected.
+    ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats));
+
+    // Now let's insert some leases into subnet 1.
+    subnet_id = 1;
+
+    // Insert three assigned NAs.
+    makeLease6(Lease::TYPE_NA, "3001:1::1", 0, subnet_id);
+    makeLease6(Lease::TYPE_NA, "3001:1::2", 0, subnet_id);
+    makeLease6(Lease::TYPE_NA, "3001:1::3", 0, subnet_id);
+    expectedStats[subnet_id - 1]["assigned-nas"] = 3;
+
+    // Insert two declined NAs.
+    makeLease6(Lease::TYPE_NA, "3001:1::4", 0, subnet_id,
+               Lease::STATE_DECLINED);
+    makeLease6(Lease::TYPE_NA, "3001:1::5", 0, subnet_id,
+               Lease::STATE_DECLINED);
+    expectedStats[subnet_id - 1]["declined-addresses"] = 2;
+
+    // Insert one expired NA.
+    makeLease6(Lease::TYPE_NA, "3001:1::6", 0, subnet_id,
+               Lease::STATE_EXPIRED_RECLAIMED);
+
+    // Insert two assigned PDs.
+    makeLease6(Lease::TYPE_PD, "3001:1:2:0100::", 112, subnet_id);
+    makeLease6(Lease::TYPE_PD, "3001:1:2:0200::", 112, subnet_id);
+    expectedStats[subnet_id - 1]["assigned-pds"] = 2;
+
+    // Insert two expired PDs.
+    makeLease6(Lease::TYPE_PD, "3001:1:2:0300::", 112, subnet_id,
+               Lease::STATE_EXPIRED_RECLAIMED);
+    makeLease6(Lease::TYPE_PD, "3001:1:2:0400::", 112, subnet_id,
+               Lease::STATE_EXPIRED_RECLAIMED);
+
+    // Now let's add leases to subnet 2.
+    subnet_id = 2;
+
+    // Insert two assigned NAs.
+    makeLease6(Lease::TYPE_NA, "2001:db81::1", 0, subnet_id);
+    makeLease6(Lease::TYPE_NA, "2001:db81::2", 0, subnet_id);
+    expectedStats[subnet_id - 1]["assigned-nas"] = 2;
+
+    // Insert one declined NA.
+    makeLease6(Lease::TYPE_NA, "2001:db81::3", 0, subnet_id,
+               Lease::STATE_DECLINED);
+    expectedStats[subnet_id - 1]["declined-addresses"] = 1;
+
+    // Now Recount the stats.
+    ASSERT_NO_THROW(lmptr_->recountLeaseStats6());
+
+    // Make sure stats are as expected.
+    ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats));
+
+    // Delete some leases and update the expected stats.
+    EXPECT_TRUE(lmptr_->deleteLease(IOAddress("3001:1::2")));
+    expectedStats[0]["assigned-nas"] = 2;
+
+    EXPECT_TRUE(lmptr_->deleteLease(IOAddress("2001:db81::3")));
+    expectedStats[1]["declined-addresses"] = 0;
+
+    // Recount the stats.
+    ASSERT_NO_THROW(lmptr_->recountLeaseStats6());
+
+    // Make sure stats are as expected.
+    ASSERT_NO_FATAL_FAILURE(checkLeaseStats(expectedStats));
+}
+
+
 }; // namespace test
 }; // namespace dhcp
 }; // namespace isc

+ 62 - 1
src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -15,6 +15,12 @@ namespace isc {
 namespace dhcp {
 namespace test {
 
+
+/// @brief typedefs to simplify lease statistic testing
+typedef std::map<std::string, int64_t> StatValMap;
+typedef std::pair<std::string, int64_t> StatValPair;
+typedef std::vector<StatValMap> StatValMapList;
+
 /// @brief Test Fixture class with utility functions for LeaseMgr backends
 ///
 /// It contains utility functions, like dummy lease creation.
@@ -94,6 +100,47 @@ public:
     /// @return vector<Lease6Ptr> Vector of pointers to leases
     std::vector<Lease6Ptr> createLeases6();
 
+    /// @brief Compares a StatsMgr statistic to an expected value
+    ///
+    /// Attempt to fetch the named statistic from the StatsMg and if
+    /// found, compare its observed value to the given value.
+    /// Fails if the stat is not found or if the values do not match.
+    ///
+    /// @param name StatsMgr name for the statistic to check
+    /// @param expected_value expected value of the statistic
+    void checkStat(const std::string& name, const int64_t expected_value);
+
+    /// @brief Compares StatsMgr statistics against an expected list of values
+    ///
+    /// Iterates over a list of statistic names and expected values, attempting
+    /// to fetch each from the StatsMgr and if found, compare its observed value
+    /// to the expected value.  Fails if any of the expected stats are not
+    /// found or if the values do not match.
+    ///
+    /// @param expected_stats Map of expected static names and values.
+    void checkLeaseStats(const StatValMapList& expected_stats);
+
+    /// @brief Constructs a minimal IPv4 lease and adds it to the lease storage
+    ///
+    /// @param address - IPv4 address for the lease
+    /// @param subnet_id - subnet ID to which the lease belongs
+    /// @param state - the state of the lease
+    void makeLease4(const std::string& address, const SubnetID& subnet_id,
+                    const uint32_t state = Lease::STATE_DEFAULT);
+
+    /// @brief Constructs a minimal IPv6 lease and adds it to the lease storage
+    ///
+    /// The DUID is constructed from the address and prefix length.
+    ///
+    /// @param type - type of lease to create (TYPE_NA, TYPE_PD...)
+    /// @param address - IPv6 address/prefix for the lease
+    /// @param prefix_len = length of the prefix (should be 0 for TYPE_NA)
+    /// @param subnet_id - subnet ID to which the lease belongs
+    /// @param state - the state of the lease
+    void makeLease6(const Lease::Type& type, const std::string& address,
+                    uint8_t prefix_len, const SubnetID& subnet_id,
+                    const uint32_t state = Lease::STATE_DEFAULT);
+
     /// @brief checks that addLease, getLease4(addr) and deleteLease() works
     void testBasicLease4();
 
@@ -313,6 +360,20 @@ public:
     /// leases can be removed.
     void testDeleteExpiredReclaimedLeases4();
 
+    /// @brief Check that the IPv4 lease statistics can be recounted
+    ///
+    /// This test creates two subnets and several leases associated with
+    /// them, then verifies that lease statistics are recalculated correctly
+    /// after altering the lease states in various ways.
+    void testRecountLeaseStats4();
+
+    /// @brief Check that the IPv6 lease statistics can be recounted
+    ///
+    /// This test creates two subnets and several leases associated with
+    /// them, then verifies that lease statistics are recalculated correctly
+    /// after altering the lease states in various ways.
+    void testRecountLeaseStats6();
+
     /// @brief String forms of IPv4 addresses
     std::vector<std::string>  straddress4_;
 

+ 10 - 0
src/lib/dhcpsrv/tests/memfile_lease_mgr_unittest.cc

@@ -1883,6 +1883,16 @@ TEST_F(MemfileLeaseMgrTest, lease6ContainerIndexUpdate) {
     }
 }
 
+// Verifies that IPv4 lease statistics can be recalculated.
+TEST_F(MemfileLeaseMgrTest, recountLeaseStats4) {
+    startBackend(V4);
+    testRecountLeaseStats4();
+}
 
+// Verifies that IPv6 lease statistics can be recalculated.
+TEST_F(MemfileLeaseMgrTest, recountLeaseStats6) {
+    startBackend(V6);
+    testRecountLeaseStats6();
+}
 
 }; // end of anonymous namespace

+ 10 - 0
src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc

@@ -477,4 +477,14 @@ TEST_F(MySqlLeaseMgrTest, deleteExpiredReclaimedLeases4) {
     testDeleteExpiredReclaimedLeases4();
 }
 
+// Verifies that IPv4 lease statistics can be recalculated.
+TEST_F(MySqlLeaseMgrTest, recountLeaseStats4) {
+    testRecountLeaseStats4();
+}
+
+// Verifies that IPv6 lease statistics can be recalculated.
+TEST_F(MySqlLeaseMgrTest, recountLeaseStats6) {
+    testRecountLeaseStats6();
+}
+
 }; // Of anonymous namespace

+ 10 - 0
src/lib/dhcpsrv/tests/pgsql_lease_mgr_unittest.cc

@@ -402,4 +402,14 @@ TEST_F(PgSqlLeaseMgrTest, getExpiredLeases6) {
     testGetExpiredLeases6();
 }
 
+// Verifies that IPv4 lease statistics can be recalculated.
+TEST_F(PgSqlLeaseMgrTest, recountLeaseStats4) {
+    testRecountLeaseStats4();
+}
+
+// Verifies that IPv6 lease statistics can be recalculated.
+TEST_F(PgSqlLeaseMgrTest, recountLeaseStats6) {
+    testRecountLeaseStats6();
+}
+
 }; // namespace