2
0
mirror of https://gitlab.isc.org/isc-projects/kea synced 2025-08-30 21:45:37 +00:00

[5589] Initial checkin

New stat_cmds hook library initial commit.
Both stat-lease4/6-get commands implemented and unit tested

modified:
    configure.ac
    src/hooks/dhcp/Makefile.am
new:
    src/hooks/dhcp/stat_cmds/.gitignore
    src/hooks/dhcp/stat_cmds/Makefile.am
    src/hooks/dhcp/stat_cmds/stat_cmds.cc
    src/hooks/dhcp/stat_cmds/stat_cmds.dox
    src/hooks/dhcp/stat_cmds/stat_cmds.h
    src/hooks/dhcp/stat_cmds/stat_cmds_callouts.cc
    src/hooks/dhcp/stat_cmds/stat_cmds_log.cc
    src/hooks/dhcp/stat_cmds/stat_cmds_log.h
    src/hooks/dhcp/stat_cmds/stat_cmds_messages.mes
    src/hooks/dhcp/stat_cmds/tests/.gitignore
    src/hooks/dhcp/stat_cmds/tests/Makefile.am
    src/hooks/dhcp/stat_cmds/tests/run_unittests.cc
    src/hooks/dhcp/stat_cmds/tests/stat_cmds_unittest.cc
    src/hooks/dhcp/stat_cmds/version.cc
This commit is contained in:
Thomas Markwalder
2018-05-09 16:00:10 -04:00
parent 593ddeae13
commit 96b0712f43
16 changed files with 2468 additions and 1 deletions

View File

@@ -1400,6 +1400,8 @@ AC_CONFIG_FILES([Makefile
src/hooks/dhcp/user_chk/Makefile
src/hooks/dhcp/user_chk/tests/Makefile
src/hooks/dhcp/user_chk/tests/test_data_files_config.h
src/hooks/dhcp/stat_cmds/Makefile
src/hooks/dhcp/stat_cmds/tests/Makefile
src/lib/Makefile
src/lib/asiodns/Makefile
src/lib/asiodns/tests/Makefile

View File

@@ -1 +1 @@
SUBDIRS = user_chk lease_cmds
SUBDIRS = user_chk lease_cmds stat_cmds

4
src/hooks/dhcp/stat_cmds/.gitignore vendored Normal file
View File

@@ -0,0 +1,4 @@
/stat_cmds_messages.cc
/stat_cmds_messages.h
/s-messages
/html

View File

@@ -0,0 +1,63 @@
SUBDIRS = . tests
AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CXXFLAGS = $(KEA_CXXFLAGS)
# Define rule to build logging source files from message file
stat_cmds_messages.h stat_cmds_messages.cc: s-messages
s-messages: stat_cmds_messages.mes
$(top_builddir)/src/lib/log/compiler/kea-msg-compiler $(top_srcdir)/src/hooks/dhcp/stat_cmds/stat_cmds_messages.mes
touch $@
# Tell automake that the message files are built as part of the build process
# (so that they are built before the main library is built).
BUILT_SOURCES = stat_cmds_messages.h stat_cmds_messages.cc
# Ensure that the message file and doxygen file is included in the distribution
EXTRA_DIST = stat_cmds_messages.mes
EXTRA_DIST += stat_cmds.dox
# Get rid of generated message files on a clean
CLEANFILES = *.gcno *.gcda stat_cmds_messages.h stat_cmds_messages.cc s-messages
# convenience archive
noinst_LTLIBRARIES = libstat_cmds.la
libstat_cmds_la_SOURCES = stat_cmds.cc stat_cmds.h
libstat_cmds_la_SOURCES += stat_cmds_callouts.cc
libstat_cmds_la_SOURCES += stat_cmds_log.cc stat_cmds_log.h
libstat_cmds_la_SOURCES += version.cc
nodist_libstat_cmds_la_SOURCES = stat_cmds_messages.cc stat_cmds_messages.h
libstat_cmds_la_CXXFLAGS = $(AM_CXXFLAGS)
libstat_cmds_la_CPPFLAGS = $(AM_CPPFLAGS)
# install the shared object into $(libdir)/hooks
lib_hooksdir = $(libdir)/hooks
lib_hooks_LTLIBRARIES = libdhcp_stat_cmds.la
libdhcp_stat_cmds_la_SOURCES =
libdhcp_stat_cmds_la_LDFLAGS = $(AM_LDFLAGS)
libdhcp_stat_cmds_la_LDFLAGS += -avoid-version -export-dynamic -module
libdhcp_stat_cmds_la_LIBADD = libstat_cmds.la
libdhcp_stat_cmds_la_LIBADD += $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
libdhcp_stat_cmds_la_LIBADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
libdhcp_stat_cmds_la_LIBADD += $(top_builddir)/src/lib/cc/libkea-cc.la
libdhcp_stat_cmds_la_LIBADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
libdhcp_stat_cmds_la_LIBADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
libdhcp_stat_cmds_la_LIBADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
libdhcp_stat_cmds_la_LIBADD += $(top_builddir)/src/lib/eval/libkea-eval.la
libdhcp_stat_cmds_la_LIBADD += $(top_builddir)/src/lib/dhcp_ddns/libkea-dhcp_ddns.la
libdhcp_stat_cmds_la_LIBADD += $(top_builddir)/src/lib/stats/libkea-stats.la
libdhcp_stat_cmds_la_LIBADD += $(top_builddir)/src/lib/dns/libkea-dns++.la
libdhcp_stat_cmds_la_LIBADD += $(top_builddir)/src/lib/cryptolink/libkea-cryptolink.la
libdhcp_stat_cmds_la_LIBADD += $(top_builddir)/src/lib/log/libkea-log.la
libdhcp_stat_cmds_la_LIBADD += $(top_builddir)/src/lib/util/threads/libkea-threads.la
libdhcp_stat_cmds_la_LIBADD += $(top_builddir)/src/lib/util/libkea-util.la
libdhcp_stat_cmds_la_LIBADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
libdhcp_stat_cmds_la_LIBADD += $(LOG4CPLUS_LIBS)
libdhcp_stat_cmds_la_LIBADD += $(CRYPTO_LIBS)
libdhcp_stat_cmds_la_LIBADD += $(BOOST_LIBS)

View File

@@ -0,0 +1,639 @@
// Copyright (C) 2018 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
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <config.h>
#include <config/command_mgr.h>
#include <config/cmds_impl.h>
#include <cc/command_interpreter.h>
#include <cc/data.h>
#include <dhcpsrv/cfgmgr.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <dhcpsrv/subnet_id.h>
#include <hooks/hooks.h>
#include <exceptions/exceptions.h>
#include <stat_cmds.h>
#include <stat_cmds_log.h>
#include <stats/stats_mgr.h>
#include <util/boost_time_utils.h>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <string>
using namespace isc::dhcp;
using namespace isc::data;
using namespace isc::config;
using namespace isc::asiolink;
using namespace isc::hooks;
using namespace isc::stats;
using namespace std;
namespace isc {
namespace stat_cmds {
/// @brief Exception thrown no subnets fall within the selection criteria
/// This exception is thrown when a valid combination of query parameters
/// excludes all known (i.e. configured) subnets.
class NotFound: public isc::Exception {
public:
NotFound (const char* file, size_t line, const char* what) :
isc::Exception(file, line, what) { };
};
///@brief Implements command handlinge for stat-lease<x>-get commands
class LeaseStatCmdsImpl : private CmdsImpl {
public:
/// @brief Wrapper class stat-lease<x>-get command parameters.
class Parameters {
public:
/// @brief Specifies the subnet-id for a single subnet, or
/// the first subnet for a subnet range
SubnetID first_subnet_id_;
/// @brief Specifies the last subnet for subnet range
SubnetID last_subnet_id_;
/// @brief Denotes the query selection mode all, subnet,
/// or subnet range
LeaseStatsQuery::SelectMode select_mode_;
};
public:
/// @brief Provides the implementation for stat-lease4-get,
/// @ref isc::stat_cmds::StatCmds::statLease4GetHandler
///
/// It parses the command arguments, and then invokes makeResult4()
/// to fulfull the lease6 statistics fetch. It then constructs the outbound
/// response based on those results. If a NotFound exception is caught,
/// a CONTROL_RESULT_EMTPY response is generated.
///
/// @param handle Callout context - which is expected to contain the
/// command JSON text in the "command" argument
/// @return 0 upon success, non-zero otherwise
int
statLease4GetHandler(CalloutHandle& handle);
/// @brief Provides the implementation for stat-lease6-get,
/// @ref isc::stat_cmds::StatCmds::statLease6GetHandler
///
/// It parses the command arguments, and then invokes makeResult6()
/// to fulfull the lease6 statistics fetch. It then constructs the outbound
/// response based on those results. If a NotFound exception is caught,
/// a CONTROL_RESULT_EMTPY response is generated.
///
/// @param handle Callout context - which is expected to contain the
/// command JSON text in the "command" argument
/// @return 0 upon success, non-zero otherwise
int
statLease6GetHandler(CalloutHandle& handle);
/// @Brief Parses command arguments into stat-lease<x>-get parameters
/// @param cmd_args Element form of command arguments to parse
/// @throw BadValue if any of the following rules are broken:
///
/// -# If subnet-id is specified it must be an integer > 0
/// -# If subnet-range is specified it must contain both
/// first-subnet-id and last-subnet-id and their values
/// must fulfill: 0 < first-subnet-id < last-subnet-id
/// -# subnet-id and subnet-range are mutually exclusive
Parameters getParameters(const ConstElementPtr& cmd_args);
/// @brief Executes the lease4 query and constructs the outbound result set
///
/// This method uses the command parameters to identify the range
/// of configured subnets. If the range contains no known subnets
/// then a NotFound exception is thrown. Having determined the range
/// to be valid, it then executes the appropriate Lease4 stats query via
/// the LeaseMgr.
///
/// Lastly, it iterates over the qualifying subnets adding a row
/// of statistics for each one to the result-set. Each row combines
/// the totals from StatsMgr with the type and state counts from the
/// query results. For subnets with no query data (i.e. no leases),
/// their rows have non-zero values for totals only.
///
/// @param result Element to which the constructed result-set will be added.
/// @param params Parsed stat-lease4-cmd parameters
/// @throw NotFound if the selection criteria eliminates all known subnets
uint64_t makeResultSet4(const ElementPtr& result, const Parameters& params);
/// @brief Executes the lease4 query and constructs the outbound result set
/// This method uses the command parameters to identify the range
/// of configured subnets. If the range contains no known subnets
/// then a NotFound exception is thrown. Having determined the range
/// to be valid, it then executes the appropriate Lease6 stats query via
/// the LeaseMgr.
///
/// Lastly, it iterates over the qualifying subnets adding a row
/// of statistics for each one to the result-set. Each row combines
/// the totals from StatsMgr with the type and state counts from the
/// query results. For subnets with no query data (i.e. no leases),
/// their rows have non-zero values for totals only.
///
/// @param result Element to which the constructed result-set will be added.
/// @param params Parsed stat-lease6-cmd parameters
/// @throw NotFound if the selection criteria eliminates all known subnets
uint64_t makeResultSet6(const ElementPtr& result, const Parameters& params);
/// @brief Instantiates a new "empty" result-set Element
///
/// Constructs a ElementPtr tree of an empty result set
/// for holding rows of the given column labels. In JSON
/// it appears as follows:
///
/// "result-set": {
/// "timestamp": "2018-03-22 09:43:30.815371",
/// "columns": ["<label-1>, <label-2>, ... ],
/// "rows": []
/// }
///
/// And then adds it to the given wrapper element.
///
/// @param wrapper Element to which the newly constructed result-set
/// will be added.
/// @param column_labels list of the column labels in the order the values
/// for each column will appear in the result-set rows
/// @return A reference to the writable list of rows of the result-set
ElementPtr createResultSet(const ElementPtr& wrapper,
const std::vector<std::string>& column_labels);
/// @brief Adds a row of Lease4 stat values to a list of value rows
///
/// @param[out] value_rows list of rows to which to add
/// @param[out] subent_id id of the subnet of the new row. This value is
/// also used for fetching the total addresses in the subnet
/// @param assigned number of assigned addresses in the subnet
/// @param assigned number of declined addresses in the subnet
void addValueRow4(ElementPtr value_rows, const SubnetID &subnet_id,
int64_t assigned, int64_t declined);
/// @brief Adds a row of Lease6 stat values to a list of value rows
///
/// @param[out] value_rows list of rows to which to add
/// @param[out] subent_id id of the subnet of the new row. This value is
/// also used for fetching the total NAs and PDs in the subnet
/// @param assigned number of assigned NAs in the subnet
/// @param declined number of declined NAs in the subnet
/// @param assigned_pds number of assigned PDs the subnet
void addValueRow6(ElementPtr value_rows, const SubnetID &subnet_id,
int64_t assigned, int64_t declined, int64_t assigned_pds);
/// @brief Fetches a single statistic for a subnet from StatsMgr
///
/// Uses the given id and name to query the StatsMgr for the desired value.
///
/// @param subnet_id id of the desired subnet
/// @param name name of the desired statistic
int64_t getSubnetStat(const SubnetID& subnet_id, const std::string& name);
};
int
LeaseStatCmdsImpl::statLease4GetHandler(CalloutHandle& handle) {
ElementPtr result = Element::createMap();
int response_code;
string txt = "malformed command";
try {
// Extract the command and then the parameters
extractCommand(handle);
Parameters params = getParameters(cmd_args_);
// Now build the result set
txt = "building result";
uint64_t rows = makeResultSet4(result, params);
// Eureka it worked!
response_code = CONTROL_RESULT_SUCCESS;
std::stringstream os;
os << "stat-lease4-get: " << rows << " rows found";
txt = os.str();
} catch (const NotFound& ex) {
// Criteria was valid but included no known subnets,
// so we return a not found response.
response_code = CONTROL_RESULT_EMPTY;
std::stringstream os;
os << "stat-lease4-get: " << "no matching data, " << ex.what();
txt = os.str();
} catch (const std::exception& ex) {
LOG_ERROR(stat_cmds_logger, STAT_LEASE4_GET_FAILED)
.arg(txt)
.arg(ex.what());
setErrorResponse(handle, ex.what());
return (1);
}
LOG_INFO(stat_cmds_logger, STAT_LEASE4_GET).arg(txt);
ConstElementPtr response = createAnswer(response_code, txt, result);
setResponse(handle, response);
return (0);
}
int
LeaseStatCmdsImpl::statLease6GetHandler(CalloutHandle& handle) {
ElementPtr result = Element::createMap();
int response_code;
string txt = "malformed command";
try {
// Extract the command and then the parameters
extractCommand(handle);
Parameters params = getParameters(cmd_args_);
// Now build the result set
txt = "building result";
uint64_t rows = makeResultSet6(result, params);
// Eureka it worked!
response_code = CONTROL_RESULT_SUCCESS;
std::stringstream os;
os << "stat-lease6-get: " << rows << " rows found";
txt = os.str();
} catch (const NotFound& ex) {
// Criteria was valid but included no known subnets,
// so we return a not found response.
result = Element::createMap();
response_code = CONTROL_RESULT_EMPTY;
std::stringstream os;
os << "stat-lease6-get: " << "no matching data, " << ex.what();
txt = os.str();
} catch (const std::exception& ex) {
LOG_ERROR(stat_cmds_logger, STAT_LEASE6_GET_FAILED)
.arg(txt)
.arg(ex.what());
setErrorResponse(handle, ex.what());
return (1);
}
LOG_INFO(stat_cmds_logger, STAT_LEASE6_GET).arg(txt);
ConstElementPtr response = createAnswer(response_code, txt, result);
setResponse(handle, response);
return (0);
}
LeaseStatCmdsImpl::Parameters
LeaseStatCmdsImpl::getParameters(const ConstElementPtr& cmd_args) {
Parameters params;
if (!cmd_args || cmd_args->getType() != Element::map) {
isc_throw(BadValue, "Parameters missing or are not a map.");
}
params.select_mode_ = LeaseStatsQuery::ALL_SUBNETS;
if (cmd_args->contains("subnet-id")) {
ConstElementPtr value = cmd_args->get("subnet-id");
if (value->getType() != Element::integer) {
isc_throw(BadValue, "'subnet-id' parameter is not integer");
}
if (value->intValue() <= 0) {
isc_throw(BadValue, "'subnet-id' parameter must be > 0");
}
params.first_subnet_id_ = value->intValue();
params.select_mode_ = LeaseStatsQuery::SINGLE_SUBNET;
}
if (cmd_args->contains("subnet-range")) {
if (params.select_mode_ == LeaseStatsQuery::SINGLE_SUBNET) {
isc_throw(BadValue, "Cannot specify both subnet-id and subnet-range");
}
ConstElementPtr range = cmd_args->get("subnet-range");
if (range->getType() != Element::map) {
isc_throw(BadValue, "subnet-range parameter is not a map");
}
ConstElementPtr value = range->get("first-subnet-id");
if (!value || value->getType() != Element::integer) {
isc_throw(BadValue, "'first-subnet-id' parameter missing or not an integer");
}
if (value->intValue() <= 0) {
isc_throw(BadValue, "'first-subnet-id' parameter must be > 0");
}
params.first_subnet_id_ = value->intValue();
value = range->get("last-subnet-id");
if (!value || value->getType() != Element::integer) {
isc_throw(BadValue, "'last-subnet-id' parameter missing or not an integer");
}
if (value->intValue() <= 0) {
isc_throw(BadValue, "'last-subnet-id' parameter must be > 0");
}
params.last_subnet_id_ = value->intValue();
if (params.last_subnet_id_ < params.first_subnet_id_) {
isc_throw(BadValue, "'last-subnet-id' must be greater than 'first-subnet-id'");
}
params.select_mode_ = LeaseStatsQuery::SUBNET_RANGE;
}
return (params);
}
uint64_t
LeaseStatCmdsImpl::makeResultSet4(const ElementPtr& result_wrapper,
const Parameters& params) {
// First we need to determine the range of configured subnets
// which meet the selection criteria. If the range contains
// no subnets we punt.
const Subnet4Collection* subnets =
CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
const auto& idx = subnets->get<SubnetSubnetIdIndexTag>();
// Init to ALL so we can use auto ;)
auto lower = idx.begin();
auto upper = idx.end();
switch (params.select_mode_) {
case LeaseStatsQuery::SINGLE_SUBNET:
lower = idx.find(params.first_subnet_id_);
// If it's an unknown subnet, punt.
if (lower == idx.end()) {
isc_throw(NotFound, "subnet-id: "
<< params.first_subnet_id_ << " does not exist");
}
upper = idx.upper_bound(params.first_subnet_id_);
break;
case LeaseStatsQuery::SUBNET_RANGE:
lower = idx.lower_bound(params.first_subnet_id_);
upper = idx.upper_bound(params.last_subnet_id_);
break;
default:
break;
}
// If it's an empty range, punt.
if (lower == upper) {
isc_throw(NotFound, "selected ID range: "
<< params.first_subnet_id_ << " through "
<< params.last_subnet_id_ << " includes no known subnets");
}
// Now, that we have a valid range, run the Lease query.
LeaseStatsQueryPtr query;
switch (params.select_mode_) {
case LeaseStatsQuery::ALL_SUBNETS:
query = LeaseMgrFactory::instance().startLeaseStatsQuery4();
break;
case LeaseStatsQuery::SINGLE_SUBNET:
query = LeaseMgrFactory::instance()
.startSubnetLeaseStatsQuery4(params.first_subnet_id_);
break;
case LeaseStatsQuery::SUBNET_RANGE:
query = LeaseMgrFactory::instance()
.startSubnetRangeLeaseStatsQuery4(params.first_subnet_id_,
params.last_subnet_id_);
break;
}
// Create the empty result-set.
std::vector<std::string>column_labels = { "subnet-id", "total-addreses",
"assigned-addreses","declined-addreses"};
ElementPtr value_rows = createResultSet(result_wrapper, column_labels);
// Get the first query row
LeaseStatsRow query_row;
bool query_eof = !(query->getNextRow(query_row));
// Now we iterate over the selected range, building rows accordingly.
for (auto cur_subnet = lower; cur_subnet != upper; ++cur_subnet) {
SubnetID cur_id = (*cur_subnet)->getID();
// Add total only rows for subnets that occur before,
// in-between, or after the subnets in the query content
if ((cur_id < query_row.subnet_id_) ||
(cur_id > query_row.subnet_id_) ||
(query_eof)) {
// Generate a totals only row
addValueRow4(value_rows, cur_id, 0, 0);
continue;
}
// Current subnet matches query row, so iterate over its
// query rows (one per state) and accumulate them
// into a result-set row.
int64_t assigned = 0;
int64_t declined = 0;
bool add_row = false;
while (!query_eof && query_row.subnet_id_ == cur_id) {
if (query_row.lease_state_ == Lease::STATE_DEFAULT) {
add_row = true;
assigned = query_row.state_count_;
} else if (query_row.lease_state_ == Lease::STATE_DECLINED) {
add_row = true;
declined = query_row.state_count_;
}
query_eof = !(query->getNextRow(query_row));
}
// Add the row for the current subnet
if (add_row) {
addValueRow4(value_rows, cur_id, assigned, declined);
}
}
return (value_rows->size());
}
uint64_t
LeaseStatCmdsImpl::makeResultSet6(const ElementPtr& result_wrapper,
const Parameters& params) {
// Iterate over the selected range of configured subnets generating
// a result-set row for each one. If a subnet has data in the query
// content use it, otherwise, it gets a row with totals only. This
// way we send back a row for every selected subnet.
const Subnet6Collection* subnets =
CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
// Set the bounds on the selected subnet range
const auto& idx = subnets->get<SubnetSubnetIdIndexTag>();
// Init to all so we can use auto ;)
auto lower = idx.begin();
auto upper = idx.end();
switch (params.select_mode_) {
case LeaseStatsQuery::SINGLE_SUBNET:
lower = idx.lower_bound(params.first_subnet_id_);
// If it's an unknown subnet, punt.
if (lower == idx.end()) {
isc_throw(NotFound, "subnet-id: "
<< params.first_subnet_id_ << " does not exist");
}
upper = idx.upper_bound(params.first_subnet_id_);
break;
case LeaseStatsQuery::SUBNET_RANGE:
lower = idx.lower_bound(params.first_subnet_id_);
upper = idx.upper_bound(params.last_subnet_id_);
break;
default:
break;
}
// If it's empty range, punt.
if (lower == upper) {
isc_throw(NotFound, "selected ID range: "
<< params.first_subnet_id_ << " through "
<< params.last_subnet_id_ << " includes no known subnets");
}
// Create the result-set map.
// labels could be class statics?
std::vector<std::string>column_labels = { "subnet-id", "total-nas", "assigned-nas",
"declined-nas", "total-pds", "assigned-pds"};
ElementPtr value_rows = createResultSet(result_wrapper, column_labels);
// Now we can run the stats query.
LeaseStatsQueryPtr query;
switch (params.select_mode_) {
case LeaseStatsQuery::ALL_SUBNETS:
query = LeaseMgrFactory::instance().startLeaseStatsQuery6();
break;
case LeaseStatsQuery::SINGLE_SUBNET:
query = LeaseMgrFactory::instance()
.startSubnetLeaseStatsQuery6(params.first_subnet_id_);
break;
case LeaseStatsQuery::SUBNET_RANGE:
query = LeaseMgrFactory::instance()
.startSubnetRangeLeaseStatsQuery6(params.first_subnet_id_,
params.last_subnet_id_);
break;
}
// Get the first query row
LeaseStatsRow query_row;
bool query_eof = !(query->getNextRow(query_row));
for (auto cur_subnet = lower; cur_subnet != upper; ++cur_subnet) {
SubnetID cur_id = (*cur_subnet)->getID();
// Add total only rows for subnets that occur before,
// in-between, or after subnets in the query content
if ((cur_id < query_row.subnet_id_) ||
(cur_id > query_row.subnet_id_) ||
(query_eof)) {
// Generate a totals only row
addValueRow6(value_rows, cur_id, 0, 0, 0);
continue;
}
// Current subnet matches query row, so iterate over
// its query rows and accumulate them into a result-set row.
int64_t assigned = 0;
int64_t declined = 0;
int64_t assigned_pds = 0;
bool add_row = false;
while (!query_eof && query_row.subnet_id_ == cur_id) {
if (query_row.lease_state_ == Lease::STATE_DEFAULT) {
add_row = true;
if (query_row.lease_type_ == Lease::TYPE_NA) {
assigned = query_row.state_count_;
} else {
assigned_pds = query_row.state_count_;
}
break;
} else if (query_row.lease_state_ == Lease::STATE_DECLINED) {
add_row = true;
declined = query_row.state_count_;
}
// Get next query row
query_eof = !(query->getNextRow(query_row));
}
if (add_row) {
addValueRow6(value_rows, cur_id, assigned, declined, assigned_pds);
}
}
return (value_rows->size());
}
ElementPtr
LeaseStatCmdsImpl::createResultSet(const ElementPtr &result_wrapper,
const std::vector<std::string>& column_labels) {
// Create the result-set map and add it to the wrapper.
ElementPtr result_set = Element::createMap();
result_wrapper->set("result-set", result_set);
// Create the timestamp based on time now and add it to the result set.
boost::posix_time::ptime now(boost::posix_time::second_clock::universal_time());
ElementPtr timestamp = Element::create(isc::util::ptimeToText(now));
result_set->set("timestamp", timestamp);
// Create the list of column names and add it to the result set.
ElementPtr columns = Element::createList();
for (auto label = column_labels.begin(); label != column_labels.end(); ++label) {
columns->add(Element::create(*label));
}
result_set->set("columns", columns);
// Create the empty value_rows list, add it and then return it.
ElementPtr value_rows = Element::createList();
result_set->set("rows", value_rows);
return (value_rows);
}
void
LeaseStatCmdsImpl::addValueRow4(ElementPtr value_rows, const SubnetID &subnet_id,
int64_t assigned, int64_t declined) {
ElementPtr row = Element::createList();
row->add(Element::create(static_cast<int64_t>(subnet_id)));
row->add(Element::create(getSubnetStat(subnet_id, "total-addresses")));
row->add(Element::create(assigned));
row->add(Element::create(declined));
value_rows->add(row);
}
void
LeaseStatCmdsImpl::addValueRow6(ElementPtr value_rows, const SubnetID &subnet_id,
int64_t assigned, int64_t declined, int64_t assigned_pds) {
ElementPtr row = Element::createList();
row->add(Element::create(static_cast<int64_t>(subnet_id)));
row->add(Element::create(getSubnetStat(subnet_id, "total-nas")));
row->add(Element::create(assigned));
row->add(Element::create(declined));
row->add(Element::create(getSubnetStat(subnet_id, "total-pds")));
row->add(Element::create(assigned_pds));
value_rows->add(row);
}
int64_t
LeaseStatCmdsImpl::getSubnetStat(const SubnetID& subnet_id, const std::string& name) {
ObservationPtr stat = StatsMgr::instance().
getObservation(StatsMgr::generateName("subnet", subnet_id, name));
if (stat) {
return (stat->getInteger().first);
}
return (0);
}
int
StatCmds::statLease4GetHandler(CalloutHandle& handle) {
LeaseStatCmdsImpl impl;
return(impl.statLease4GetHandler(handle));
}
int
StatCmds::statLease6GetHandler(CalloutHandle& handle) {
LeaseStatCmdsImpl impl;
return(impl.statLease6GetHandler(handle));
}
StatCmds::StatCmds() {
}
};
};

View File

@@ -0,0 +1,98 @@
// Copyright (C) 2018 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
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
/**
@page libdhcp_stat_cmds Kea Stat Commands Hooks Library
@section libdhcp_stat_cmdsIntro Introduction
Welcome to Kea Stat Commands Hooks Library. This documentation is addressed to
developers who are interested in the internal operation of the Stat Commands
library. This file provides information needed to understand and perhaps extend
this library.
This documentation is stand-alone: you should have read and understood <a
href="http://kea.isc.org/docs/devel/">Kea Developer's Guide</a> and in
particular its section about hooks.
@section stat_cmds Stat Commands Overview
Stat Commands (or stat_cmds) is a Hook library that can be loaded by Kea to
extend it with additional mechanisms.
The primary purpose of this library is to provide commands that manage leases.
As such, the whole library is structured around command handlers. When the
library is loaded it registers a number of handlers for new commands. When a
command is issued (be it directly via control channel or indirectly via REST
interface from control agent), the code receives a JSON command with
parameters. Those are parsed and then actual operation commences. This
operation always interacts with an instantiation of isc::dhcp::StatMgr
instance, which is Kea's way of storing leases. At the time of writing this text
(Aug. 2017), Kea supports four types of lease managers: memfile, MySQL,
PostgreSQL or Cassandra. The lease commands provided by this library
provide a unified interface for those backends.
As with other hooks, this one also keeps its code in a separate namespace which
corresponds to the file name of the library: isc::stat_cmds.
@section stat_cmdsCode Stat Commands Code Overview
The library operation starts with Kea calling the load() function (file
load_unload.cc). It instantiates an isc::stat_cmds::StatCmds object.
The constructor of that object registers all of the lease commands. For a list,
see @ref isc::stat_cmds::StatCmds class documentation. This class uses Pimpl
design pattern, thus the real implementation is hidden in isc::stat_cmds::StatCmdsImpl.
Almost every command has its own handler, except few that share the same handler
between v4 and v6 due to its similarity. For example
isc::stat_cmds::StatCmdsImpl::leaseAddHandler handles lease4-add and lease6-add
commands. Although the details differ between handlers, the general approach
is the same. First, it starts with parameters sanitization and then some
interaction with isc::dhcp::StatMgr is conducted.
For commands that do something with a specific lease (lease4-get, lease6-get,
lease4-del, lease6-del), there is a @ref isc::stat_cmds::StatCmdsImpl::Parameters
class that contains parsed elements.
For details see documentation and code of the following handlers:
- @ref isc::stat_cmds::StatCmdsImpl::leaseAddHandler (lease4-add, lease6-add)
- @ref isc::stat_cmds::StatCmdsImpl::leaseGetHandler (lease4-get, lease6-get)
- @ref isc::stat_cmds::StatCmdsImpl::lease4DelHandler (lease4-del)
- @ref isc::stat_cmds::StatCmdsImpl::lease6DelHandler (lease6-del)
- @ref isc::stat_cmds::StatCmdsImpl::lease4UpdateHandler (lease4-update)
- @ref isc::stat_cmds::StatCmdsImpl::lease6UpdateHandler (lease6-update)
- @ref isc::stat_cmds::StatCmdsImpl::lease4WipeHandler (lease4-wipe)
- @ref isc::stat_cmds::StatCmdsImpl::lease6WipeHandler (lease6-wipe)
@section stat_cmdsDesigns Stat Commands Design choices
The lease manipulation commands were implemented to provide a convenient interface
for sysadmins. The primary goal was to offer a way to interact with the live
lease database in unified way, regardless of the actual backend being used.
For some backends (MySQL, PostgreSQL and Cassandra) it is possible to interact
directly with the backend while Kea is running and possibly change its content. This
ability is both powerful and dangerous. In particular, only rudimentary
checks are enforced by the DB schemas (e.g. not possible to have two leases
for the same address). However, it does not prevent sysadmins from making
more obscure errors, like inserting leases for subnets that do not exist
or configuring an address that is topologically outside of the subnet to which
it should belong. These kind of checks are only possible by DHCP-aware
code, which this library provides.
Some of the queries may require a seemingly odd set of parameters. For example,
lease6-get query requires at least DUID, subnet-id and IAID to retrieve a lease
by DUID. The need for a sysadmin to know and specify an IAID is troublesome.
However, the guiding principle here was to use whatever queries were already
exposed by the lease manager and not introduce new indexes, unless absolutely
necessary. This ensures that there is no performance degradation when the
library is loaded. The only exception for that was lease4-wipe and lease6-wipe
commands that remove all leases from specific subnet. As there were no
queries that could retrieve or otherwise enumerate leases for a specific subnet,
a new query type and a new index had to be added.
*/

View File

@@ -0,0 +1,131 @@
// Copyright (C) 2017-2018 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
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#ifndef STAT_CMDS_H
#define STAT_CMDS_H
#include <cc/data.h>
#include <hooks/hooks.h>
#include <boost/shared_ptr.hpp>
namespace isc {
namespace stat_cmds {
/// @brief Forward declaration of implementation class.
class StatCmdsImpl;
/// @brief Implements the logic for processing commands pertaining to
/// stat manipulation.
///
/// This class is used by the callouts implementing command handlers for
/// stat manipulations.
class StatCmds {
public:
/// @brief Constructor.
///
/// It creates an instance of the @c StatCmdsImpl.
StatCmds();
/// @brief stat-lease4-get command handler
///
/// This command attempts to fetch lease4 statistics for one or
/// more subnets based upon subnet selection criteria (or lack thereof).
/// It extracts the command name and arguments from the given Callouthandle,
/// attempts to process them, and then set's the handle's "response"
/// arguments accordingly.
/// {
/// "command": "stat-lease4-get",
/// "arguments": {
/// "from_storage: true/false, // optional - maybe?
/// "subnet-id": x, // optional
/// "subnet-id-range": { // optional
/// "first-subnet-id": x, // id >= x
/// "last-subnet-id": y // id <= x
/// }
/// }
/// }
///
/// It produces a response as described below:
///
/// {
/// "result": 0,
/// "text": "<message>",
/// "arguments": {
/// "result-set": {
/// "timestamp": "2018-03-22 09:43:30.815371",
/// "columns": ["subnet_id", "total-addresses",
/// "assigned-addresses", "declined-addresses"],
/// "rows": [
/// [1, 600, 450, 3],
/// :
/// ]
/// }
/// }
/// }
///
/// @param handle Callout context - which is expected to contain the
///
/// add command JSON text in the "command" argument
/// @return result of the operation
int
statLease4GetHandler(hooks::CalloutHandle& handle);
/// @brief stat-lease6-get command handler
///
/// This command attempts to fetch lease6 statistics for one or
/// more subnets based upon subnet selection criteria (or lack thereof).
/// It extracts the command name and arguments from the given Callouthandle,
/// attempts to process them, and then set's the handle's "response"
/// argument accordingly.
/// {
/// "command": "stat-lease6-get",
/// "arguments": {
/// "from_storage: true/false,
/// "subnet-id": x, // optional
/// "subnet-id-range": { // optional
/// "first-subnet-id": x, // id >= x
/// "last-subnet-id": y // id <= x
/// }
/// }
/// }
///
/// It produces a response as described below:
///
/// {
/// "result": 0,
/// "text": "<message>",
/// "arguments": {
/// "result-set": {
/// "timestamp": "2018-03-22 09:43:30.815371",
/// "columns": ["subnet_id", "total-nas",
/// "assigned-nas", "declined-nas",
/// "total-pds", "assigned-pds"],
/// "rows": [
/// [1, 600, 450, 3, 64, 10],
/// :
/// ]
/// }
/// }
/// }
///
///
/// @param handle Callout context - which is expected to contain the
/// add command JSON text in the "command" argument
/// @return result of the operation
int
statLease6GetHandler(hooks::CalloutHandle& handle);
#if 0
private:
/// Pointer to the actual implementation
boost::shared_ptr<StatCmdsImpl> impl_;
#endif
};
};
};
#endif

View File

@@ -0,0 +1,64 @@
// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the End User License
// Agreement. See COPYING file in the premium/ directory.
// Functions accessed by the hooks framework use C linkage to avoid the name
// mangling that accompanies use of the C++ compiler as well as to avoid
// issues related to namespaces.
#include <stat_cmds.h>
#include <stat_cmds_log.h>
#include <cc/command_interpreter.h>
#include <hooks/hooks.h>
using namespace isc::config;
using namespace isc::data;
using namespace isc::hooks;
using namespace isc::stat_cmds;
extern "C" {
/// @brief This is a command callout for 'stat-lease4-get' command.
///
/// @param handle Callout handle used to retrieve a command and
/// provide a response.
/// @return 0 if this callout has been invoked successfully,
/// 1 otherwise.
int stat_lease4_get(CalloutHandle& handle) {
StatCmds stat_cmds;
return(stat_cmds.statLease4GetHandler(handle));
}
/// @brief This is a command callout for 'stat-lease6-get' command.
///
/// @param handle Callout handle used to retrieve a command and
/// provide a response.
/// @return 0 if this callout has been invoked successfully,
/// 1 otherwise.
int stat_lease6_get(CalloutHandle& handle) {
StatCmds stat_cmds;
return(stat_cmds.statLease6GetHandler(handle));
}
/// @brief This function is called when the library is loaded.
///
/// @param handle library handle
/// @return 0 when initialization is successful, 1 otherwise
int load(LibraryHandle& handle) {
handle.registerCommandCallout("stat-lease4-get", stat_lease4_get);
handle.registerCommandCallout("stat-lease6-get", stat_lease6_get);
LOG_INFO(stat_cmds_logger, STAT_CMDS_INIT_OK);
return (0);
}
/// @brief This function is called when the library is unloaded.
///
/// @return 0 if deregistration was successful, 1 otherwise
int unload() {
LOG_INFO(stat_cmds_logger, STAT_CMDS_DEINIT_OK);
return (0);
}
} // end extern "C"

View File

@@ -0,0 +1,16 @@
// Copyright (C) 2018 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
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <stat_cmds_log.h>
namespace isc {
namespace stat_cmds {
isc::log::Logger stat_cmds_logger("stat_cmds_hooks");
}
}

View File

@@ -0,0 +1,23 @@
// Copyright (C) 2018 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
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#ifndef STAT_CMD_LOG_H
#define STAT_CMD_LOG_H
#include <log/logger_support.h>
#include <log/macros.h>
#include <stat_cmds_messages.h>
namespace isc {
namespace stat_cmds {
extern isc::log::Logger stat_cmds_logger;
} // end of isc::stat_cmds
} // end of isc namespace
#endif

View File

@@ -0,0 +1,35 @@
# Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
% STAT_LEASE4_GET stat-lease4-get command successful (parameters: %1)
The stat-lease4-get command has been successful. Parameters of the command
are logged.
% STAT_LEASE4_GET_FAILED stat-lease4-get command failed (parameters: %1, reason: %2)
The stat-lease4-get command has failed. Both the reason as well as the
parameters passed are logged.
% STAT_LEASE6_GET stat-lease4-get command successful (parameters: %1)
The stat-lease4-get command has been successful. Parameters of the command
are logged.
% STAT_LEASE6_GET_FAILED stat-lease4-get command failed (parameters: %1, reason: %2)
The stat-lease4-get command has failed. Both the reason as well as the
parameters passed are logged.
% STAT_CMDS_DEINIT_FAILED unloading Stat Commands hooks library failed: %1
This error message indicates an error during unloading the Lease Commands
hooks library. The details of the error are provided as argument of
the log message.
% STAT_CMDS_DEINIT_OK unloading Stat Commands hooks library successful
This info message indicates that the Stat Commands hooks library has been
removed successfully.
% STAT_CMDS_INIT_FAILED loading Stat Commands hooks library failed: %1
This error message indicates an error during loading the Lease Commands
hooks library. The details of the error are provided as argument of
the log message.
% STAT_CMDS_INIT_OK loading Stat Commands hooks library successful
This info message indicates that the Stat Commands hooks library has been
loaded successfully. Enjoy!

View File

@@ -0,0 +1,5 @@
stat_cmds_unittests
stat_cmds_unittests.log
stat_cmds_unittests.trs
test-suite.log
*~

View File

@@ -0,0 +1,56 @@
SUBDIRS = .
AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
AM_CPPFLAGS += -I$(top_builddir)/src/hooks/dhcp/stat_cmds -I$(top_srcdir)/src/hooks/dhcp/stat_cmds
AM_CPPFLAGS += $(BOOST_INCLUDES)
AM_CPPFLAGS += -DSTAT_CMDS_LIB_SO=\"$(abs_top_builddir)/src/hooks/dhcp/stat_cmds/.libs/libdhcp_stat_cmds.so\"
AM_CPPFLAGS += -DINSTALL_PROG=\"$(abs_top_srcdir)/install-sh\"
AM_CXXFLAGS = $(KEA_CXXFLAGS)
if USE_STATIC_LINK
AM_LDFLAGS = -static
endif
# Unit test data files need to get installed.
EXTRA_DIST =
CLEANFILES = *.gcno *.gcda
# TESTS_ENVIRONMENT = $(LIBTOOL) --mode=execute $(VALGRIND_COMMAND)
LOG_COMPILER = $(LIBTOOL)
AM_LOG_FLAGS = --mode=execute
TESTS =
if HAVE_GTEST
TESTS += stat_cmds_unittests
stat_cmds_unittests_SOURCES = run_unittests.cc
stat_cmds_unittests_SOURCES += stat_cmds_unittest.cc
stat_cmds_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
stat_cmds_unittests_LDFLAGS = $(AM_LDFLAGS) $(CRYPTO_LDFLAGS) $(GTEST_LDFLAGS)
stat_cmds_unittests_CXXFLAGS = $(AM_CXXFLAGS)
stat_cmds_unittests_LDADD = $(top_builddir)/src/lib/dhcpsrv/libkea-dhcpsrv.la
stat_cmds_unittests_LDADD += $(top_builddir)/src/lib/dhcp/libkea-dhcp++.la
stat_cmds_unittests_LDADD += $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
stat_cmds_unittests_LDADD += $(top_builddir)/src/lib/cc/libkea-cc.la
stat_cmds_unittests_LDADD += $(top_builddir)/src/lib/config/libkea-cfgclient.la
stat_cmds_unittests_LDADD += $(top_builddir)/src/lib/hooks/libkea-hooks.la
stat_cmds_unittests_LDADD += $(top_builddir)/src/lib/log/libkea-log.la
stat_cmds_unittests_LDADD += $(top_builddir)/src/lib/util/libkea-util.la
stat_cmds_unittests_LDADD += $(top_builddir)/src/lib/exceptions/libkea-exceptions.la
stat_cmds_unittests_LDADD += $(LOG4CPLUS_LIBS)
stat_cmds_unittests_LDADD += $(CRYPTO_LIBS)
stat_cmds_unittests_LDADD += $(BOOST_LIBS)
stat_cmds_unittests_LDADD += $(GTEST_LDADD)
if HAVE_CQL
stat_cmds_unittests_LDFLAGS += $(CQL_LIBS)
endif
endif
noinst_PROGRAMS = $(TESTS)

View File

@@ -0,0 +1,17 @@
// Copyright (C) 2017 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
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <log/logger_support.h>
#include <gtest/gtest.h>
int
main(int argc, char* argv[]) {
::testing::InitGoogleTest(&argc, argv);
isc::log::initLogger();
int result = RUN_ALL_TESTS();
return (result);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,17 @@
// Copyright (C) 2018 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
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <config.h>
#include <hooks/hooks.h>
extern "C" {
/// @brief returns Kea hooks version.
int version() {
return (KEA_HOOKS_VERSION);
}
}