Browse Source

[4294] Unit tests and MySql now support IPv6 lease stat recounting

src/lib/dhcpsrv/cfg_subnets6.cc
    - CfgSubnets6::removeStatistics() - added removal of declined stats
    - CfgSubnets6::updateStatistics() - added call to recountAddressStats6()

src/lib/dhcpsrv/mysql_lease_mgr.h
src/lib/dhcpsrv/mysql_lease_mgr.cc
    - Added TaggedStatement RECOUNT_LEASE6_STATS
    - MySqlAddressStatsQuery6 - new MySql derivation of AddressStatsQuery6
    - MySqlLeaseMgr::startAddressStatsQuery6() - new virtual method which
    creates and starts a MySqlAddressStatsQuery6

src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.h
src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc
    - GenericLeaseMgrTest::checkAddressStats4 renamed to checkAddressStats as
    it applies to either v4 or v6
    - GenericLeaseMgrTest::makeLease6() - new method which creates a minimal
    IPv6 lease and adds it to lease storage
    - GenericLeaseMgrTest::testRecountAddressStats6() - new method which
    checks IPv6 lease stats recounting

src/lib/dhcpsrv/tests/mysql_lease_mgr_unittest.cc
    - TEST_F(MySqlLeaseMgrTest, recountAddressStats6) - new test
Thomas Markwalder 8 years ago
parent
commit
6f56be5aa2

+ 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().recountAddressStats6();
     }
 }
 

+ 2 - 2
src/lib/dhcpsrv/lease_mgr.cc

@@ -156,7 +156,7 @@ LeaseMgr::recountAddressStats6() {
                            zero);
 
         stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id,
-                                                  "declined-nas"),
+                                                  "declined-addresses"),
                            zero);
 
         stats_mgr.setValue(StatsMgr::
@@ -189,7 +189,7 @@ LeaseMgr::recountAddressStats6() {
                         stats_mgr.setValue(StatsMgr::
                                            generateName("subnet",
                                                         row.subnet_id_,
-                                                        "declined-nas"),
+                                                        "declined-addresses"),
                                            row.state_count_);
 
                         // Add to the global value.

+ 154 - 3
src/lib/dhcpsrv/mysql_lease_mgr.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2012-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
@@ -206,8 +206,14 @@ TaggedStatement tagged_statements[] = {
                         "state = ? "
                             "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"},
+     "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" },
+
     // End of list sentinel
     {MySqlLeaseMgr::NUM_STATEMENTS, NULL}
 };
@@ -1353,9 +1359,154 @@ MySqlLeaseMgr::startAddressStatsQuery4() {
     return(query);
 }
 
+/// @brief MySql derivation of the IPv6 statistical lease data query
+///
+/// This class is used to recalculate IPv6 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 subnet, ordered
+/// by subnet id in ascending order.
+///
+class MySqlAddressStatsQuery6 : public AddressStatsQuery6 {
+public:
+    /// @brief Constructor
+    ///
+    /// @param conn A open connection to the database housing the lease data
+    MySqlAddressStatsQuery6(MySqlConnection& conn);
 
+    /// @brief Destructor
+    virtual ~MySqlAddressStatsQuery6();
 
+    /// @brief Creates the IPv6 lease statistical data result set
+    ///
+    /// The result set is populated by executing an SQL query against the
+    /// lease4 table which sums the leases per lease state per subnet id.
+    /// The query used is the prepared statement identified by
+    /// MySqlLeaseMgr::RECOUNT_LEASE6_STATS.  This method creates the binds
+    /// the statement to the output bind array  and then executes the
+    /// statement.
+    void start();
 
+    /// @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(AddressStatsRow6& row);
+
+private:
+
+    /// @brief Analyzes the given statement outcome status
+    ///
+    /// Wrapper method around the MySqlConnection:checkError() that is
+    /// used to generate the appropriate exception if the status indicates
+    /// an error.
+    ////
+    /// a DbOperation error
+    /// @param status The MySQL statement execution outcome status
+    /// @param what invocation context message which will be included in
+    /// any exception
+    void checkError(int status, const char* what) const;
+
+    /// @brief Database connection to use to execute the query
+    MySqlConnection& conn_;
+
+    /// @brief The query's prepared statement
+    MYSQL_STMT *statement_;
+
+    /// @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_;
+};
+
+MySqlAddressStatsQuery6::MySqlAddressStatsQuery6(MySqlConnection& conn)
+    : conn_(conn), statement_(conn_.statements_[MySqlLeaseMgr
+                                                ::RECOUNT_LEASE6_STATS]),
+      bind_(4) {
+}
+
+MySqlAddressStatsQuery6::~MySqlAddressStatsQuery6() {
+    (void) mysql_stmt_free_result(statement_);
+}
+
+void
+MySqlAddressStatsQuery6::start() {
+    // subnet_id: unsigned int
+    bind_[0].buffer_type = MYSQL_TYPE_LONG;
+    bind_[0].buffer = reinterpret_cast<char*>(&subnet_id_);
+    bind_[0].is_unsigned = MLM_TRUE;
+
+    // lease type:  uint32_t
+    bind_[1].buffer_type = MYSQL_TYPE_LONG;
+    bind_[1].buffer = reinterpret_cast<char*>(&lease_type_);
+    bind_[1].is_unsigned = MLM_TRUE;
+
+    // state:  uint32_t
+    bind_[2].buffer_type = MYSQL_TYPE_LONG;
+    bind_[2].buffer = reinterpret_cast<char*>(&lease_state_);
+    bind_[2].is_unsigned = MLM_TRUE;
+
+    // state_count_: uint32_t
+    bind_[3].buffer_type = MYSQL_TYPE_LONG;
+    bind_[3].buffer = reinterpret_cast<char*>(&state_count_);
+    bind_[3].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]);
+    checkError(status, "RECOUNT_LEASE6_STATS: outbound binding failed");
+
+    // Execute the statement
+    status = mysql_stmt_execute(statement_);
+    checkError(status, "RECOUNT_LEASE6_STATS: 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_);
+    checkError(status, "RECOUNT_LEASE6_STATS: results storage setup failed");
+}
+
+bool
+MySqlAddressStatsQuery6::getNextRow(AddressStatsRow6& 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_ = static_cast<Lease::LeaseState>(lease_state_);
+        row.state_count_ = state_count_;
+        have_row = true;
+    } else if (status != MYSQL_NO_DATA) {
+        checkError(status, "RECOUNT_LEASE6_STATS: getNextRow failed");
+    }
+
+    return (have_row);
+}
+
+void
+MySqlAddressStatsQuery6::checkError(int status, const char* what) const {
+    conn_.checkError(status, MySqlLeaseMgr::RECOUNT_LEASE6_STATS, what);
+}
+
+AddressStatsQuery6Ptr
+MySqlLeaseMgr::startAddressStatsQuery6() {
+    AddressStatsQuery6Ptr query(new MySqlAddressStatsQuery6(conn_));
+    query->start();
+    return(query);
+}
 
 // MySqlLeaseMgr Constructor and Destructor
 

+ 13 - 2
src/lib/dhcpsrv/mysql_lease_mgr.h

@@ -409,7 +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 address statisics
+        RECOUNT_LEASE4_STATS,        // Fetches IPv4 address statisics
+        RECOUNT_LEASE6_STATS,        // Fetches IPv6 address statisics
         NUM_STATEMENTS               // Number of statements
     };
 
@@ -596,10 +597,20 @@ private:
     /// 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 AddressStatsQuery4
     virtual AddressStatsQuery4Ptr startAddressStatsQuery4();
 
+    /// @brief Creates and runs the IPv6 lease stats query
+    ///
+    /// It creates an instance of a MySqlAddressStatsQuery6 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 AddressStatsQuery6
+    virtual AddressStatsQuery6Ptr startAddressStatsQuery6();
+
     /// @brief Check Error and Throw Exception
     ///
     /// This method invokes @ref MySqlConnection::checkError.

+ 149 - 5
src/lib/dhcpsrv/tests/generic_lease_mgr_unittest.cc

@@ -2401,7 +2401,7 @@ GenericLeaseMgrTest::checkStat(const std::string& name,
 }
 
 void
-GenericLeaseMgrTest::checkAddressStats4(const StatValMapList& expectedStats) {
+GenericLeaseMgrTest::checkAddressStats(const StatValMapList& expectedStats) {
     // Global accumulators
     int64_t declined_addresses = 0;
     int64_t declined_reclaimed_addresses = 0;
@@ -2451,6 +2451,26 @@ GenericLeaseMgrTest::makeLease4(const std::string& address,
 }
 
 void
+GenericLeaseMgrTest::makeLease6(const Lease::Type& type,
+                                const std::string& address,
+                                uint8_t prefix_len,
+                                const SubnetID& subnet_id,
+                                const Lease::LeaseState& 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::testRecountAddressStats4() {
     using namespace stats;
 
@@ -2489,13 +2509,13 @@ GenericLeaseMgrTest::testRecountAddressStats4() {
     }
 
     // Make sure stats are as expected.
-    ASSERT_NO_FATAL_FAILURE(checkAddressStats4(expectedStats));
+    ASSERT_NO_FATAL_FAILURE(checkAddressStats(expectedStats));
 
     // Recount stats.  We should have the same results.
     ASSERT_NO_THROW(lmptr_->recountAddressStats4());
 
     // Make sure stats are as expected.
-    ASSERT_NO_FATAL_FAILURE(checkAddressStats4(expectedStats));
+    ASSERT_NO_FATAL_FAILURE(checkAddressStats(expectedStats));
 
     // Now let's insert some leases into subnet 1.
     int subnet_id = 1;
@@ -2529,7 +2549,7 @@ GenericLeaseMgrTest::testRecountAddressStats4() {
     ASSERT_NO_THROW(lmptr_->recountAddressStats4());
 
     // Make sure stats are as expected.
-    ASSERT_NO_FATAL_FAILURE(checkAddressStats4(expectedStats));
+    ASSERT_NO_FATAL_FAILURE(checkAddressStats(expectedStats));
 
     // Delete some leases from subnet, and update the expected stats.
     EXPECT_TRUE(lmptr_->deleteLease(IOAddress("192.0.1.1")));
@@ -2542,9 +2562,133 @@ GenericLeaseMgrTest::testRecountAddressStats4() {
     ASSERT_NO_THROW(lmptr_->recountAddressStats4());
 
     // Make sure stats are as expected.
-    ASSERT_NO_FATAL_FAILURE(checkAddressStats4(expectedStats));
+    ASSERT_NO_FATAL_FAILURE(checkAddressStats(expectedStats));
 }
 
+void
+GenericLeaseMgrTest::testRecountAddressStats6() {
+    using namespace stats;
+
+    StatsMgr::instance().removeAll();
+
+    // create subnets
+    CfgSubnets6Ptr cfg =
+        CfgMgr::instance().getStagingCfg()->getCfgSubnets6();
+
+    // Create 3 subnets.
+    Subnet6Ptr subnet;
+    Pool6Ptr pool;
+    int num_subnets = 2;
+    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(checkAddressStats(expectedStats));
+
+
+    // Recount stats.  We should have the same results.
+    ASSERT_NO_THROW(lmptr_->recountAddressStats4());
+
+    // Make sure stats are as expected.
+    ASSERT_NO_FATAL_FAILURE(checkAddressStats(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_->recountAddressStats6());
+
+    // Make sure stats are as expected.
+    ASSERT_NO_FATAL_FAILURE(checkAddressStats(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_->recountAddressStats6());
+
+    // Make sure stats are as expected.
+    ASSERT_NO_FATAL_FAILURE(checkAddressStats(expectedStats));
+}
+
+
 }; // namespace test
 }; // namespace dhcp
 }; // namespace isc

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

@@ -118,7 +118,7 @@ public:
     /// do not match.
     ///
     /// @param expected_stats Map of expected static names and values.
-    void checkAddressStats4(const StatValMapList& expected_stats);
+    void checkAddressStats(const StatValMapList& expected_stats);
 
     /// @brief Constructs a minimal IPv4 lease and adds it to the lease storage
     ///
@@ -128,6 +128,19 @@ public:
     void makeLease4(const std::string& address, const SubnetID& subnet_id,
                     const Lease::LeaseState& 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 Lease::LeaseState& state = Lease::STATE_DEFAULT);
+
     /// @brief checks that addLease, getLease4(addr) and deleteLease() works
     void testBasicLease4();
 
@@ -354,6 +367,13 @@ public:
     /// after altering the lease states in various ways.
     void testRecountAddressStats4();
 
+    /// @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 testRecountAddressStats6();
+
     /// @brief String forms of IPv4 addresses
     std::vector<std::string>  straddress4_;
 

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

@@ -482,4 +482,9 @@ TEST_F(MySqlLeaseMgrTest, recountAddressStats4) {
     testRecountAddressStats4();
 }
 
+// Verifies that IPv6 lease statistics can be recalculated.
+TEST_F(MySqlLeaseMgrTest, recountAddressStats6) {
+    testRecountAddressStats6();
+}
+
 }; // Of anonymous namespace