-// 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
#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>
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"));
}
}
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();
}
}
zero);
stats_mgr.setValue(StatsMgr::generateName("subnet", subnet_id,
- "declined-nas"),
+ "declined-addresses"),
zero);
stats_mgr.setValue(StatsMgr::
stats_mgr.setValue(StatsMgr::
generateName("subnet",
row.subnet_id_,
- "declined-nas"),
+ "declined-addresses"),
row.state_count_);
// Add to the global value.
-// 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
"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}
};
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
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
};
/// 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.
}
void
-GenericLeaseMgrTest::checkAddressStats4(const StatValMapList& expectedStats) {
+GenericLeaseMgrTest::checkAddressStats(const StatValMapList& expectedStats) {
// Global accumulators
int64_t declined_addresses = 0;
int64_t declined_reclaimed_addresses = 0;
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 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;
}
// 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;
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")));
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
/// 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
///
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();
/// 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_;
testRecountAddressStats4();
}
+// Verifies that IPv6 lease statistics can be recalculated.
+TEST_F(MySqlLeaseMgrTest, recountAddressStats6) {
+ testRecountAddressStats6();
+}
+
}; // Of anonymous namespace