///
/// @note This is done in two phases: first the content of the
/// vendor-class-identifier option is used as a class, by
- /// calling @ref classifyByVendor(). Second classification match
+ /// calling (private) classifyByVendor(). Second classification match
/// expressions are evaluated. The resulting classes will be stored
/// in the packet (see @ref isc::dhcp::Pkt4::classes_ and
/// @ref isc::dhcp::Pkt4::inClass).
libperfmon_la_SOURCES += alarm.cc alarm.h
libperfmon_la_SOURCES += monitored_duration_store.cc monitored_duration_store.h
libperfmon_la_SOURCES += alarm_store.cc alarm_store.h
+libperfmon_la_SOURCES += perfmon_config.cc perfmon_config.h
+libperfmon_la_SOURCES += perfmon_mgr.cc perfmon_mgr.h
libperfmon_la_SOURCES += version.cc
libperfmon_la_CXXFLAGS = $(AM_CXXFLAGS)
(subnet_id_ < other.subnet_id_));
}
+std::ostream&
+operator<<(std::ostream& os, const DurationKey& key) {
+ os << key.getLabel();
+ return (os);
+}
// MonitoredDuration methods
/// @brief Get a composite label of the member values with text message types.
///
- /// @param family Protocol family of the key (AF_INET or AF_INET6)
/// The format of the string:
///
/// @code
isc::dhcp::SubnetID subnet_id_;
};
+std::ostream&
+operator<<(std::ostream& os, const DurationKey& key);
+
/// @brief Defines a pointer to a DurationKey instance.
typedef boost::shared_ptr<DurationKey> DurationKeyPtr;
int dhcp4_srv_configured(CalloutHandle& /* handle */) {
// We do this here rather than in load() to ensure we check after the
- // filter has been determined.
+ // packet filter has been determined.
LOG_DEBUG(perfmon_logger, DBGLVL_TRACE_BASIC,
PERFMON_DHCP4_SOCKET_RECEIVED_TIME_SUPPORT)
.arg(IfaceMgr::instance().isSocketReceivedTimeSupported() ? "Yes" : "No");
int dhcp6_srv_configured(CalloutHandle& /* handle */) {
// We do this here rather than in load() to ensure we check after the
- // filter has been determined.
+ // packet filter has been determined.
LOG_DEBUG(perfmon_logger, DBGLVL_TRACE_BASIC,
PERFMON_DHCP6_SOCKET_RECEIVED_TIME_SUPPORT)
.arg(IfaceMgr::instance().isSocketReceivedTimeSupported() ? "Yes" : "No");
--- /dev/null
+// Copyright (C) 2024 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 <perfmon_config.h>
+#include <dhcp/dhcp6.h>
+#include <dhcp/pkt4.h>
+#include <dhcp/pkt6.h>
+
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace boost::posix_time;
+
+namespace isc {
+namespace perfmon {
+
+const data::SimpleKeywords
+DurationKeyParser::CONFIG_KEYWORDS =
+{
+ { "query-type", Element::string },
+ { "response-type", Element::string },
+ { "start-event", Element::string },
+ { "stop-event", Element::string },
+ { "subnet-id", Element::integer }
+};
+
+uint16_t
+DurationKeyParser::getMessageNameType4(const std::string& name) {
+ static std::map<std::string, uint16_t> name_type_map = {
+ {"", DHCP_NOTYPE},
+ {"DHCPDISCOVER", DHCPDISCOVER},
+ {"DHCPOFFER", DHCPOFFER},
+ {"DHCPREQUEST", DHCPREQUEST},
+ {"DHCPDECLINE", DHCPDECLINE},
+ {"DHCPACK", DHCPACK},
+ {"DHCPNAK", DHCPNAK},
+ {"DHCPRELEASE", DHCPRELEASE},
+ {"DHCPINFORM", DHCPINFORM},
+ {"DHCPLEASEQUERY", DHCPLEASEQUERY},
+ {"DHCPLEASEUNASSIGNED", DHCPLEASEUNASSIGNED},
+ {"DHCPLEASEUNKNOWN", DHCPLEASEUNKNOWN},
+ {"DHCPLEASEACTIVE", DHCPLEASEACTIVE},
+ {"DHCPBULKLEASEQUERY", DHCPBULKLEASEQUERY},
+ {"DHCPLEASEQUERYDONE", DHCPLEASEQUERYDONE},
+ {"DHCPLEASEQUERYSTATUS", DHCPLEASEQUERYSTATUS},
+ {"DHCPTLS", DHCPTLS}
+ };
+
+ try {
+ const auto& found = name_type_map.at(name);
+ return (found);
+ } catch (const std::out_of_range& ex) {
+ isc_throw(BadValue, "'" << name << "' is not a valid DHCP message type");
+ }
+}
+
+uint16_t
+DurationKeyParser::getMessageNameType6(const std::string& name) {
+ static std::map<std::string, uint16_t> name_type_map = {
+ {"", DHCPV6_NOTYPE},
+ {"SOLICIT", DHCPV6_SOLICIT},
+ {"ADVERTISE", DHCPV6_ADVERTISE},
+ {"REQUEST", DHCPV6_REQUEST},
+ {"CONFIRM", DHCPV6_CONFIRM},
+ {"RENEW", DHCPV6_RENEW},
+ {"REBIND", DHCPV6_REBIND},
+ {"REPLY", DHCPV6_REPLY},
+ {"RELEASE", DHCPV6_RELEASE},
+ {"DECLINE", DHCPV6_DECLINE},
+ {"RECONFIGURE", DHCPV6_RECONFIGURE},
+ {"INFORMATION_REQUEST", DHCPV6_INFORMATION_REQUEST},
+ {"RELAY_FORW", DHCPV6_RELAY_FORW},
+ {"RELAY_REPL", DHCPV6_RELAY_REPL},
+ {"LEASEQUERY", DHCPV6_LEASEQUERY},
+ {"LEASEQUERY_REPLY", DHCPV6_LEASEQUERY_REPLY},
+ {"LEASEQUERY_DONE", DHCPV6_LEASEQUERY_DONE},
+ {"LEASEQUERY_DATA", DHCPV6_LEASEQUERY_DATA},
+ {"RECONFIGURE_REQUEST", DHCPV6_RECONFIGURE_REQUEST},
+ {"RECONFIGURE_REPLY", DHCPV6_RECONFIGURE_REPLY},
+ {"DHCPV4_QUERY", DHCPV6_DHCPV4_QUERY},
+ {"DHCPV4_RESPONSE", DHCPV6_DHCPV4_RESPONSE},
+ {"ACTIVELEASEQUERY", DHCPV6_ACTIVELEASEQUERY},
+ {"STARTTLS", DHCPV6_STARTTLS},
+ {"BNDUPD", DHCPV6_BNDUPD},
+ {"BNDREPLY", DHCPV6_BNDREPLY},
+ {"POOLREQ", DHCPV6_POOLREQ},
+ {"POOLRESP", DHCPV6_POOLRESP},
+ {"UPDREQ", DHCPV6_UPDREQ},
+ {"UPDREQALL", DHCPV6_UPDREQALL},
+ {"UPDDONE", DHCPV6_UPDDONE},
+ {"CONNECT", DHCPV6_CONNECT},
+ {"CONNECTREPLY", DHCPV6_CONNECTREPLY},
+ {"DISCONNECT", DHCPV6_DISCONNECT},
+ {"STATE", DHCPV6_STATE},
+ {"CONTACT", DHCPV6_CONTACT}
+ };
+
+ try {
+ const auto& found = name_type_map.at(name);
+ return(found);
+ } catch (const std::out_of_range& ex) {
+ isc_throw(BadValue, "'" << name << "' is not a valid DHCPV6 message type");
+ }
+}
+
+uint16_t
+DurationKeyParser::getMessageType(data::ConstElementPtr config, uint16_t family,
+ const std::string param_name, bool required /*= true */) {
+ // Parse members.
+ uint16_t msg_type = 0;
+ ConstElementPtr elem = config->get(param_name);
+ if (elem) {
+ try {
+ msg_type = (family == AF_INET ? getMessageNameType4(elem->stringValue())
+ : getMessageNameType6(elem->stringValue()));
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, "'" << param_name << "' parameter is invalid, " << ex.what());
+ }
+ } else {
+ if (required) {
+ isc_throw(DhcpConfigError, "'" << param_name << "' parameter is required");
+ }
+ }
+
+ return (msg_type);
+}
+
+DurationKeyPtr
+DurationKeyParser::parse(data::ConstElementPtr config, uint16_t family) {
+ // Note checkKeywords() will throw DhcpConfigError if there is a problem.
+ SimpleParser::checkKeywords(CONFIG_KEYWORDS, config);
+
+ // Parse members.
+ auto query_type = getMessageType(config, family, "query-type");
+
+ auto response_type = getMessageType(config, family, "response-type");
+
+ std::string start_event;
+ ConstElementPtr elem = config->get("start-event");
+ if (elem) {
+ start_event = elem->stringValue();
+ } else {
+ isc_throw(DhcpConfigError, "'start-event' parameter is required");
+ }
+
+ std::string stop_event;
+ elem = config->get("stop-event");
+ if (elem) {
+ stop_event = elem->stringValue();
+ } else {
+ isc_throw(DhcpConfigError, "'stop-event' parameter is required");
+ }
+
+ SubnetID subnet_id = SUBNET_ID_GLOBAL;
+ elem = config->get("subnet-id");
+ if (elem) {
+ subnet_id = static_cast<SubnetID>(elem->intValue());
+ }
+
+ return (DurationKeyPtr(new DurationKey(family, query_type, response_type,
+ start_event, stop_event, subnet_id)));
+}
+
+data::ElementPtr
+DurationKeyParser::toElement(DurationKeyPtr key) {
+ if (!key) {
+ isc_throw(BadValue, "DurationKeyParser::toElement() - key is empty");
+ }
+
+ ElementPtr map = Element::createMap();
+ if (key->getFamily() == AF_INET) {
+ map->set("query-type", Element::create(Pkt4::getName(key->getQueryType())));
+ map->set("response-type", Element::create(Pkt4::getName(key->getResponseType())));
+ } else {
+ map->set("query-type", Element::create(Pkt6::getName(key->getQueryType())));
+ map->set("response-type", Element::create(Pkt6::getName(key->getResponseType())));
+ }
+
+ map->set("start-event", Element::create(key->getStartEventLabel()));
+ map->set("stop-event", Element::create(key->getStopEventLabel()));
+ map->set("subnet-id", Element::create(static_cast<long long>(key->getSubnetId())));
+ return (map);
+}
+
+const data::SimpleKeywords
+AlarmParser::CONFIG_KEYWORDS =
+{
+ {"duration-key", Element::map},
+ {"enable-alarm", Element::boolean},
+ {"high-water-ms", Element::integer},
+ {"low-water-ms", Element::integer}
+};
+
+AlarmPtr
+AlarmParser::parse(data::ConstElementPtr config, uint16_t family) {
+ // Note checkKeywords() will throw DhcpConfigError if there is a problem.
+ SimpleParser::checkKeywords(CONFIG_KEYWORDS, config);
+
+ // First parse the duration-key.
+ ConstElementPtr elem = config->get("duration-key");
+ if (!elem) {
+ isc_throw(DhcpConfigError, "'duration-key'" <<" parameter is required");
+ }
+
+ DurationKeyPtr key = DurationKeyParser::parse(elem, family);
+
+ // Parse scalar members.
+ elem = config->get("enable-alarm");
+ bool enable_alarm = (elem ? elem->boolValue() : true);
+
+ elem = config->get("high-water-ms");
+ uint64_t high_water_ms = 0;
+ if (elem) {
+ int64_t value = elem->intValue();
+ if (value <= 0) {
+ isc_throw(DhcpConfigError, "high-water-ms: '"
+ << value << "', must be greater than 0");
+ }
+
+ high_water_ms = value;
+ } else {
+ isc_throw(DhcpConfigError, "'high-water-ms'" <<" parameter is required");
+ }
+
+ elem = config->get("low-water-ms");
+ uint64_t low_water_ms = 0;
+ if (elem) {
+ int64_t value = elem->intValue();
+ if (value <= 0) {
+ isc_throw(DhcpConfigError, "low-water-ms: '"
+ << value << "', must be greater than 0");
+ }
+
+ low_water_ms = value;
+ } else {
+ isc_throw(DhcpConfigError, "'low-water-ms'" <<" parameter is required");
+ }
+
+ if (low_water_ms >= high_water_ms) {
+ isc_throw(DhcpConfigError, "'low-water-ms': " << low_water_ms
+ << ", must be less than 'high-water-ms': " << high_water_ms);
+ }
+
+ return (AlarmPtr(new Alarm(*key, milliseconds(low_water_ms),
+ milliseconds(high_water_ms), enable_alarm)));
+}
+
+const data::SimpleKeywords
+PerfMonConfig::CONFIG_KEYWORDS =
+{
+ { "enable-monitoring", Element::boolean },
+ { "interval-width-secs", Element::integer },
+ { "stats-mgr-reporting", Element::boolean },
+ { "alarm-report-secs", Element::integer},
+ { "alarms", Element::list}
+};
+
+PerfMonConfig::PerfMonConfig(uint16_t family)
+ : family_(family),
+ enable_monitoring_(true),
+ interval_width_secs_(60),
+ stats_mgr_reporting_(true),
+ alarm_report_secs_(300) {
+ if (family_ != AF_INET && family_ != AF_INET6) {
+ isc_throw (BadValue, "PerfmonConfig: family must be AF_INET or AF_INET6");
+ }
+
+ alarm_store_.reset(new AlarmStore(family_));
+}
+
+void
+PerfMonConfig::parse(data::ConstElementPtr config) {
+ // Use a local instance to collect values. This way we
+ // avoid corrupting current values if there are any errors.
+ PerfMonConfig local(family_);
+
+ // Note checkKeywords() will throw DhcpConfigError if there is a problem.
+ SimpleParser::checkKeywords(CONFIG_KEYWORDS, config);
+
+ // Parse members.
+ ConstElementPtr elem = config->get("enable-monitoring");
+ if (elem) {
+ local.setEnableMonitoring(elem->boolValue());
+ }
+
+ elem = config->get("interval-width-secs");
+ if (elem) {
+ int64_t value = elem->intValue();
+ if (value <= 0) {
+ isc_throw(DhcpConfigError, "invalid interval-width-secs: '"
+ << value << "', must be greater than 0");
+ }
+
+ local.setIntervalWidthSecs(value);
+ }
+
+ elem = config->get("stats-mgr-reporting");
+ if (elem) {
+ local.setStatsMgrReporting(elem->boolValue());
+ }
+
+ elem = config->get("alarm-report-secs");
+ if (elem) {
+ int64_t value = elem->intValue();
+ if (value < 0) {
+ isc_throw(DhcpConfigError, "invalid alarm-report-secs: '"
+ << value << "', cannot be less than 0");
+ }
+
+ local.setAlarmReportSecs(value);
+ }
+
+ elem = config->get("alarms");
+ if (elem) {
+ local.parseAlarms(elem);
+ }
+
+ // All values good, shallow copy from local instance.
+ *this = local;
+}
+
+void
+PerfMonConfig::parseAlarms(data::ConstElementPtr config) {
+ alarm_store_.reset(new AlarmStore(family_));
+ for (auto const& alarm_elem : config->listValue()) {
+ try {
+ AlarmPtr alarm = AlarmParser::parse(alarm_elem, family_);
+ alarm_store_->addAlarm(alarm);
+ } catch (const std::exception& ex) {
+ isc_throw(DhcpConfigError, "cannot add Alarm to store: " << ex.what());
+ }
+ }
+}
+
+} // end of namespace perfmon
+} // end of namespace isc
--- /dev/null
+// Copyright (C) 2024 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 PERFMON_CONFIG_H
+#define PERFMON_CONFIG_H
+
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <alarm_store.h>
+#include <monitored_duration.h>
+
+namespace isc {
+namespace perfmon {
+
+/// @file perfmon_config.h The classes herein parse PerfMon hook library's 'parameters' element
+/// depicted below:
+///
+/// @code
+/// {
+/// "library": "lib/kea/hooks/libdhcp_perf_mon.so",
+/// "parameters": {
+/// "enable-monitoring" : true,
+/// "interval-width-secs" : 5,
+/// "stats-mgr-reporting" : true,
+/// "alarm-report-secs" : 600,
+/// "alarms": [
+/// {
+/// "duration-key": {
+/// "query-type" : "DHCPDISCOVER",
+/// "response-type" : "DHCPOFFER",
+/// "start-event" : "process-started",
+/// "stop-event" : "process-completed",
+/// "subnet-id" : 0
+/// },
+/// "enable-alarm" : true,
+/// "high-water-ms" : 500,
+/// "low-water-ms" : 25,
+/// },
+/// ..
+/// }]
+/// }
+/// }
+/// @endcode
+
+/// @brief Parses configuration parameters for a single DurationKey
+///
+/// DurationKey is used to identify both MonitoredDurations and
+/// Alarms, thus they could be use to define either in configuration
+/// as well as identifiy either in API calls. Given this, it seems
+/// prudent to define a "duration-key" element with its own
+/// parser.
+class DurationKeyParser {
+public:
+ /// @brief List of valid parameters and expected types.
+ static const data::SimpleKeywords CONFIG_KEYWORDS;
+
+ /// @brief Constructor
+ explicit DurationKeyParser() = default;
+
+ /// @brief Destructor
+ ~DurationKeyParser() = default;
+
+ /// @brief Convert a configuration parameter to family-specific message type
+ ///
+ /// @param config element map containing the duration key parameters.
+ /// @param family protocol family AF_INET or AF_INET6
+ /// @param param_name configuration parameter name
+ /// @param required if true then function will throw if the parameter does
+ /// not exist in the configuration. Defaults to true.
+ ///
+ /// @return numeric message type, returns DHCP_NOTYPE if name is empty.
+ /// @throw DhcpConfigError if parameter type or value is not valid, or when
+ /// requrred is true and the parameter is not in the map.
+ static uint16_t getMessageType(data::ConstElementPtr config,
+ uint16_t family, const std::string param_name,
+ bool required = true);
+
+ /// @brief Convert string message name to DHCP message type
+ ///
+ /// @param name upper-case message name (e.g "DHCPDISCOVER", "DHCPOFFER")
+ ///
+ /// @return numeric message type, returns DHCP_NOTYPE if name is empty.
+ /// @throw BadValue if the message name is unknown.
+ static uint16_t getMessageNameType4(const std::string& name);
+
+ /// @brief Convert string message name to DHCP6 message type
+ ///
+ /// @param name upper-case message name (e.g "DHCPV6_SOLICIT", "DHCV6_REPLY")
+ ///
+ /// @return numeric message type, returns DHCPV6_NOTYPE if name is empty.
+ /// @throw BadValue if the message name is unknown.
+ static uint16_t getMessageNameType6(const std::string& name);
+
+ /// @brief Convert a map of Elements into a DurationKey.
+ ///
+ /// @param config element map containing the duration key parameters.
+ /// @param family protocol family AF_INET or AF_INET6
+ static DurationKeyPtr parse(data::ConstElementPtr config, uint16_t family);
+
+ /// @brief Convert a DurationKey into a map of Elements.
+ ///
+ /// @param key DurationKey to convert.
+ ///
+ /// @return Pointer to a map of elements.
+ static data::ElementPtr toElement(DurationKeyPtr key);
+};
+
+/// @brief Parses configuration parameters for a single Alarm
+class AlarmParser {
+public:
+ /// @brief List of valid parameters and expected types.
+ static const data::SimpleKeywords CONFIG_KEYWORDS;
+
+ /// @brief Constructor
+ explicit AlarmParser();
+
+ /// @brief Destructor
+ ~AlarmParser() = default;
+
+ /// @brief
+ ///
+ /// @param config element map containing the alarm parameters.
+ /// @param family protocol family AF_INET or AF_INET6
+ static AlarmPtr parse(data::ConstElementPtr config, uint16_t family);
+};
+
+/// @brief Houses the PerfMon configuration parameters for a single scope
+/// (e.g. global, subnet...);
+class PerfMonConfig {
+public:
+ /// @brief List of valid parameters and expected types.
+ static const data::SimpleKeywords CONFIG_KEYWORDS;
+
+ /// @brief List of valid parameter defaults.
+ static const data::SimpleDefaults SIMPLE_DEFAULTS;
+
+ /// @brief Constructor
+ explicit PerfMonConfig(uint16_t family);
+
+ /// @brief Destructor
+ virtual ~PerfMonConfig() = default;
+
+ /// @brief Extracts member values from an Element::map
+ ///
+ /// @param config map of configuration parameters
+ ///
+ /// @throw DhcpConfigError if invalid values are detected.
+ void parse(data::ConstElementPtr config);
+
+ /// @brief Re-creates the AlarmStore and populates it by parsing a
+ /// list of alarm elements.
+ ///
+ /// @param config list of alarm configuration elements
+ ///
+ /// @throw DhcpConfigError if a parsing error occurs or
+ /// there are duplicate alarm keys.
+ void parseAlarms(data::ConstElementPtr config);
+
+ /// @brief Fetches the value of enable-monitoring
+ ///
+ /// @return boolean value of enable-monitoring
+ bool getEnableMonitoring() const {
+ return (enable_monitoring_);
+ };
+
+ /// @brief Sets the value of enable-monitoring
+ ///
+ /// @param value new value for enable-monitoring
+ void setEnableMonitoring(bool value) {
+ enable_monitoring_ = value;
+ }
+
+ /// @brief Fetches the value of interval-width-secs
+ ///
+ /// @return integer value of interval-width-secs
+ uint32_t getIntervalWidthSecs() const {
+ return (interval_width_secs_);
+ }
+
+ /// @brief Sets the value of interval-width-secs
+ ///
+ /// @param value new value for interval-width-secs
+ void setIntervalWidthSecs(uint32_t value) {
+ interval_width_secs_ = value;
+ }
+
+ /// @brief Fetches the value of stats-mgr-reporting
+ ///
+ /// @return boolean value of stats-mgr-reporting
+ bool getStatsMgrReporting() const {
+ return (stats_mgr_reporting_);
+ };
+
+ /// @brief Sets the value of stats-mgr-reporting
+ ///
+ /// @param value new value for stats-mgr-reporting
+ void setStatsMgrReporting(bool value) {
+ stats_mgr_reporting_ = value;
+ }
+
+ /// @brief Fetches the value of alarm-report-secs
+ ///
+ /// @return integer value of alarm-report-secs
+ uint32_t getAlarmReportSecs() const {
+ return (alarm_report_secs_);
+ }
+
+ /// @brief Sets the value of alarm-report-secs
+ ///
+ /// @param value new value for alarm-report-secs
+ void setAlarmReportSecs(uint32_t value) {
+ alarm_report_secs_ = value;
+ }
+
+ /// @brief Get protocol family
+ ///
+ /// @return uint16_t containing the family (AF_INET or AF_INET6)
+ uint16_t getFamily() {
+ return (family_);
+ }
+
+ /// @brief Get the alarm store
+ ///
+ /// @return pointer to the alarm store
+ AlarmStorePtr getAlarmStore() {
+ return alarm_store_;
+ }
+
+protected:
+ /// @brief Protocol family AF_INET or AF_INET6.
+ uint16_t family_;
+
+ /// @brief If true, performance data is processed/reported. Defaults to
+ /// true. If false the library loads and configures but does nothing.
+ /// Gives users a way to keep the library loaded without it being active.
+ /// Should be accessible via explicit API command.
+ bool enable_monitoring_;
+
+ /// @brief Number of seconds a duration accumulates samples until reporting.
+ /// Defaults to 60.
+ uint32_t interval_width_secs_;
+
+ /// @brief If true durations report to StatsMgr at the end of each interval.
+ /// Defaults to true.
+ bool stats_mgr_reporting_;
+
+ /// @brief Nubmer of seconds between reports of a raised alarm.
+ /// Defaults to 300. A value of zero disables alarms.
+ uint32_t alarm_report_secs_;
+
+ /// @brief Stores the configured alarms.
+ AlarmStorePtr alarm_store_;
+};
+
+/// @brief Defines a shared pointer to a PerfMonConfig.
+typedef boost::shared_ptr<PerfMonConfig> PerfMonConfigPtr;
+
+} // end of namespace perfmon
+} // end of namespace isc
+
+#endif
--- /dev/null
+// Copyright (C) 2024 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/.
+
+// 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 <config.h>
+
+#include <perfmon_mgr.h>
+
+namespace isc {
+namespace perfmon {
+
+using namespace isc::data;
+using namespace boost::posix_time;
+
+PerfMonMgr::PerfMonMgr(uint16_t family_)
+ : PerfMonConfig(family_) {
+ // Set defaults.
+ interval_duration_ = seconds(interval_width_secs_);
+ alarm_report_interval_ = seconds(alarm_report_secs_);
+ duration_store_.reset(new MonitoredDurationStore(family_, interval_duration_));
+}
+
+void PerfMonMgr::configure(const ConstElementPtr & params) {
+ if (!params) {
+ isc_throw(dhcp::DhcpConfigError, "params must not be null");
+ return;
+ }
+
+ if (params->getType() != Element::map) {
+ isc_throw(dhcp::DhcpConfigError, "params must be an Element::map");
+ return;
+ }
+
+ // Parse 'parameters' map.
+ try {
+ parse(params);
+ } catch (std::exception& ex) {
+ isc_throw(dhcp::DhcpConfigError,
+ "PerfMonMgr::configure failed - " << ex.what());
+ }
+
+ // Set convenience values.
+ interval_duration_ = seconds(interval_width_secs_);
+ alarm_report_interval_ = seconds(alarm_report_secs_);
+
+ // Re-create the duration store.
+ duration_store_.reset(new MonitoredDurationStore(family_, interval_duration_));
+}
+
+void PerfMonMgr::processPktEventStack(isc::dhcp::PktPtr /* query */,
+ isc::dhcp::PktPtr /* response */,
+ const isc::dhcp::SubnetID& /* subnet_id */) {
+ isc_throw (NotImplemented, __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__);
+}
+
+void
+PerfMonMgr::addDurationSample(DurationKeyPtr key, const Duration& sample) {
+ // Update duration - duration is only returned if its time to report.
+ MonitoredDurationPtr duration = duration_store_->addDurationSample(key, sample);
+ if (duration) {
+ // Report to stat mgr, returns average duration.
+ Duration average = reportToStatsMgr(duration);
+
+ // Check the average against an alarm, if one exists.
+ AlarmPtr alarm = alarm_store_->checkDurationSample(duration, average, alarm_report_interval_);
+
+ // If an alarm had a reportable outcome, report it.
+ if (alarm) {
+ reportAlarm(alarm, average);
+ }
+ }
+}
+
+Duration
+PerfMonMgr::reportToStatsMgr(MonitoredDurationPtr /* duration */) {
+ isc_throw (NotImplemented, __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__);
+}
+
+void
+PerfMonMgr::reportAlarm(AlarmPtr /* alarm */, const Duration& /* average */) {
+ isc_throw (NotImplemented, __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__);
+}
+
+void
+PerfMonMgr::reportTimerExpired() {
+ isc_throw (NotImplemented, __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__);
+}
+
+void
+PerfMonMgr::setNextReportExpiration() {
+ isc_throw (NotImplemented, __FILE__ << ":" << __LINE__ << ":" << __FUNCTION__);
+}
+
+} // end of namespace perfmon
+} // end of namespace isc
--- /dev/null
+// Copyright (C) 2024 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/.
+
+// 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.
+
+#ifndef PERFMON_MGR_H
+#define PERFMON_MGR_H
+
+#include <perfmon_config.h>
+#include <monitored_duration_store.h>
+#include <asiolink/io_service.h>
+#include <asiolink/interval_timer.h>
+
+namespace isc {
+namespace perfmon {
+
+/// @brief Singleton which provides overall configuration, control, and state of
+/// the PerfMon hook library. It owns the MonitoredDurationStore and AlarmStore
+/// instances and supplies callout and command API handlers. It derives from
+/// PerfMonConfig.
+class PerfMonMgr : public PerfMonConfig {
+public:
+ /// @brief Constructor
+ ///
+ /// @param family Protocol family AF_INET or AF_INET6.
+ explicit PerfMonMgr(uint16_t family);
+
+ /// @brief Destructor
+ virtual ~PerfMonMgr() = default;
+
+ /// @brief Parses the hook library 'parameters' element.
+ ///
+ /// @param params map of configuration parameters to parse.
+ void configure(const isc::data::ConstElementPtr& params);
+
+ /// @brief Processes the event stack of a query packet
+ ///
+ /// @todo DETAILS TO FOLLOW
+ ///
+ /// @param query query packet whose stack is to be processed
+ /// @param response response packet generated for the query
+ /// @param subnet_id id of the selected subnet
+ void processPktEventStack(isc::dhcp::PktPtr query,
+ isc::dhcp::PktPtr response,
+ const isc::dhcp::SubnetID& subnet_id);
+
+ /// @brief Adds a duration sample to a MonitoredDuration
+ ///
+ /// The MonitoredDuration identified by the given key is fetched from
+ /// the store and updated with the sample. If the update returns the
+ /// duration this means it is time to report the duration via StatsMgr.
+ /// The reported average is then checked against an alarm, if one exists.
+ /// If the check returns the alarm, then the alarm has undergone a
+ /// reportable event and is passed to reporting.
+ ///
+ /// @param key identifies the duration to update
+ /// @param sample amount of time that elapsed between the two events
+ /// identified in the key
+ void addDurationSample(DurationKeyPtr key, const Duration& sample);
+
+ /// @brief Emits an entry to StatsMgr for a given duration
+ ///
+ /// Calculates the average duration for the reportable interval and
+ /// reports the value to StatsMgr if stat-mgr-reporting is true.
+ ///
+ /// @param duration duration to report
+ ///
+ /// @return Always returns the average duration for reportable interval.
+ Duration reportToStatsMgr(MonitoredDurationPtr duration);
+
+ /// @brief Emits a report for a given alarm
+ ///
+ /// Emits a WARN log if the alarm state is TRIGGERED or an
+ /// INFO log if it is CLEARED. This may expand in the future to
+ /// accomodate additional reporting mechanisms.
+ ///
+ /// @param alarm Alarm to report
+ /// @param average Duration average which caused the state transition.
+ void reportAlarm(AlarmPtr alarm, const Duration& average);
+
+ /// @brief Handler invoked when the report timer expires.
+ ///
+ /// Fetches a list of the durations which are overdue to report and submits
+ /// them for reporting.
+ void reportTimerExpired();
+
+ /// @brief Updates the report timer.
+ ///
+ /// MonitoredDurationPtr next = durations->getReportsNext()
+ /// if next
+ /// reschedule report timer for (next->getIntervalStart() + interval_duration_);
+ /// else
+ /// cancel report timer
+ void setNextReportExpiration();
+
+ /// @brief Get the interval duration.
+ ///
+ /// @return interval-width-secs as a Duration.
+ Duration getIntervalDuration() {
+ return (interval_duration_);
+ }
+
+ /// @brief Get the alarm report interval.
+ ///
+ /// @return alarm-report-secs as a Duration.
+ Duration getAlarmReportInterval() {
+ return (alarm_report_interval_);
+ }
+
+ /// @brief Get the duration store
+ ///
+ /// @return pointer to the duration store
+ MonitoredDurationStorePtr getDurationStore() {
+ return (duration_store_);
+ }
+
+private:
+ /// @brief Length of time a MonitoredDuration accumulates samples until reporting.
+ Duration interval_duration_;
+
+ /// @brief Length of time between raised Alarm reports.
+ /// It's a conversion of alarm-report-secs to a Duration set during configuration
+ /// parsing.
+ Duration alarm_report_interval_;
+
+ /// @brief In-memory store of MonitoredDurations.
+ MonitoredDurationStorePtr duration_store_;
+
+ /// @todo Not sure if we really care. When not in service, traffic will
+ /// effectively stop. Any active durations will eventually report once via
+ /// timer but nothing more until traffic resumes.
+ ///
+ /// @brief Tracks whether or not the server is processing DHCP packets.
+ ///dhcp::NetworkStatePtr network_state_;
+
+ /// @brief IOService instance used to the timer.
+ asiolink::IOServicePtr io_service_;
+
+ /// @brief Timer which tracks the next duration due to report.
+ asiolink::IntervalTimerPtr report_timer_;
+
+ /// @brief The mutex used to protect internal state.
+ const boost::scoped_ptr<std::mutex> mutex_;
+};
+
+/// @brief Defines a shared pointer to a PerfMonMgr.
+typedef boost::shared_ptr<PerfMonMgr> PerfMonMgrPtr;
+
+} // end of namespace perfmon
+} // end of namespace isc
+
+#endif
perfmon_unittests_SOURCES += alarm_unittests.cc
perfmon_unittests_SOURCES += monitored_duration_store_unittests.cc
perfmon_unittests_SOURCES += alarm_store_unittests.cc
+perfmon_unittests_SOURCES += perfmon_config_unittests.cc
+perfmon_unittests_SOURCES += perfmon_mgr_unittests.cc
+perfmon_unittests_SOURCES += duration_key_parser_unittests.cc
+perfmon_unittests_SOURCES += alarm_parser_unittests.cc
perfmon_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES) $(LOG4CPLUS_INCLUDES)
--- /dev/null
+// Copyright (C) 2024 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/.
+
+/// @file This file contains tests which exercise the PerfmonConfig class.
+
+#include <config.h>
+#include <dhcp/dhcp6.h>
+#include <perfmon_config.h>
+#include <testutils/gtest_utils.h>
+
+#include <gtest/gtest.h>
+#include <list>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::perfmon;
+using namespace boost::posix_time;
+
+namespace {
+
+// These tests excerise AlarmParser which, with the help of DurationKeyParser
+// (tested rigourously elsewhere), parses a map of paramters as shown below:
+//
+// {
+// "duration-key": {
+// "query-type" : "DHCPDISCOVER",
+// "response-type" : "DHCPOFFER",
+// "start-event" : "process-started",
+// "stop-event" : "process-completed",
+// "subnet-id" : 70
+// },
+// "enable-alarm" : true,
+// "high-water-ms" : 500,
+// "low-water-ms" : 25,
+// }
+
+/// @brief Describes a valid test scenario.
+struct ValidScenario {
+ int line_; // Scenario line number
+ std::string json_; // JSON configuration to parse
+ Alarm::State exp_state_; // Expected value for Alarm::state
+ uint64_t exp_high_water_ms_; // Expected value for high-water-ms
+ uint64_t exp_low_water_ms_; // Expected value for low-water-ms
+};
+
+/// @brief Describes an invalid test scenario.
+struct InvalidScenario {
+ int line_; // Scenario line number
+ std::string json_; // JSON configuration to parse
+ std::string exp_message_; // Expected error text
+};
+
+/// @brief Base class test fixture for testing AlarmParser.
+class AlarmParserTest: public ::testing::Test {
+public:
+ /// @brief Constructor.
+ explicit AlarmParserTest(uint16_t family) : family_(family) {
+ }
+
+ /// @brief Destructor.
+ virtual ~AlarmParserTest() = default;
+
+ /// @brief Prepends json for a family-valid 'duration-key' to json text.
+ ///
+ /// @param scenario_json text to prepend with the duration-key.
+ ///
+ /// @return string containing the prepended text.
+ virtual std::string makeValidKeyConfig(const std::string& scenario_json) = 0;
+
+ /// @brief Runs a list of valid configurations through AlarmParser::parse().
+ void testValidScenarios() {
+ // List of test scenarios to run.
+ const std::list<ValidScenario> scenarios = {
+ {
+ // All parameters
+ __LINE__,
+ R"(
+ "enable-alarm" : true,
+ "high-water-ms" : 500,
+ "low-water-ms" : 25
+ )",
+ Alarm::CLEAR, 500, 25
+ },
+ {
+ // No enable-alarm, should default to CLEAR state.
+ __LINE__,
+ R"(
+ "high-water-ms" : 500,
+ "low-water-ms" : 25
+ )",
+ Alarm::CLEAR, 500, 25
+ },
+ {
+ // State should be DISALBED when enable-alarm is false
+ __LINE__,
+ R"(
+ "enable-alarm" : false,
+ "high-water-ms" : 500,
+ "low-water-ms" : 25
+ )",
+ Alarm::DISABLED, 500, 25
+ }
+ };
+
+ // Iterate over the scenarios.
+ for (auto const& scenario : scenarios) {
+ stringstream oss;
+ oss << "scenario at line: " << scenario.line_;
+ SCOPED_TRACE(oss.str());
+
+ // Construct valid key + scenario JSON
+ auto json = makeValidKeyConfig(scenario.json_);
+
+ // Convert JSON text to Element map.
+ ConstElementPtr json_elements;
+ ASSERT_NO_THROW(json_elements = Element::fromJSON(json))
+ << " json: " << json;
+
+ // Parsing should succeed.
+ AlarmPtr alarm;
+ ASSERT_NO_THROW_LOG(alarm = AlarmParser::parse(json_elements, family_));
+
+ // Verify expected values.
+ ASSERT_TRUE(alarm);
+ ASSERT_EQ(*alarm, *expected_key_);
+
+ EXPECT_EQ(alarm->getState(), scenario.exp_state_);
+ EXPECT_EQ(alarm->getHighWater(), milliseconds(scenario.exp_high_water_ms_));
+ EXPECT_EQ(alarm->getLowWater(), milliseconds(scenario.exp_low_water_ms_));
+ }
+ }
+
+ /// @brief Test scenarios that have valid duration-key elements but flawed
+ /// Alarm scalar parameters.
+ void testInvalidAlarmScenarios() {
+ // List of test scenarios to run. These will be prepended with a valid,
+ // family-specific duration-key element prior to parsing.
+ list<InvalidScenario> scenarios = {
+ {
+ // Spurious parameter
+ __LINE__,
+ R"(
+ "enable-alarm" : true,
+ "high-water-ms" : 500,
+ "low-water-ms" : 25,
+ "bogus": true
+ )",
+ "spurious 'bogus' parameter"
+ },
+ {
+ // Invalid type enable-alarm
+ __LINE__,
+ R"(
+ "enable-alarm" : "bogus",
+ "high-water-ms" : 500,
+ "low-water-ms" : 25,
+ "bogus": true
+ )",
+ "'enable-alarm' parameter is not a boolean"
+ },
+ {
+ // Missing high-water-ms
+ __LINE__,
+ R"(
+ "enable-alarm" : true,
+ "low-water-ms" : 25
+ )",
+ "'high-water-ms' parameter is required"
+ },
+ {
+ // Invalid type for high-water-ms
+ __LINE__,
+ R"(
+ "enable-alarm" : true,
+ "high-water-ms" : "bogus",
+ "low-water-ms" : 25
+ )",
+ "'high-water-ms' parameter is not an integer"
+ },
+ {
+ // Missing low-water-ms
+ __LINE__,
+ R"(
+ "enable-alarm" : true,
+ "high-water-ms" : 500
+ )",
+ "'low-water-ms' parameter is required"
+ },
+ {
+ // Invalid type for low-water-ms
+ __LINE__,
+ R"(
+ "enable-alarm" : true,
+ "high-water-ms" : 500,
+ "low-water-ms" : "bogus"
+ )",
+ "'low-water-ms' parameter is not an integer"
+ },
+ {
+ // Invalid threshold combination
+ __LINE__,
+ R"(
+ "enable-alarm" : true,
+ "high-water-ms" : 25,
+ "low-water-ms" : 500
+ )",
+ "'low-water-ms': 500, must be less than 'high-water-ms': 25"
+ },
+ };
+
+ testInvalidScenarios(scenarios, true);
+ }
+
+ /// @brief Runs a list of invalid configurations through AlarmParser::parse().
+ ///
+ /// @param list of valid scenarios to run
+ /// @param add_key When true, scenario json will be prepended with valid, family-specific
+ /// duration-key element prior to parsing.
+ void testInvalidScenarios(std::list<InvalidScenario>& scenarios,
+ bool add_key = true) {
+ // Iterate over the scenarios.
+ for (auto const& scenario : scenarios) {
+ stringstream oss;
+ oss << "scenario at line: " << scenario.line_;
+ SCOPED_TRACE(oss.str());
+
+ // If add_key is true prepend the scenario with valid key json
+ auto json = (add_key ? makeValidKeyConfig(scenario.json_) : scenario.json_);
+
+ // Convert JSON texts to Element map.
+ ConstElementPtr json_elements;
+ ASSERT_NO_THROW_LOG(json_elements = Element::fromJSON(json));
+
+ // Parsing elements should succeed.
+ ASSERT_THROW_MSG(AlarmParser::parse(json_elements, family_), DhcpConfigError,
+ scenario.exp_message_);
+ }
+ }
+
+ /// @brief Protocol family AF_INET or AF_INET6
+ uint16_t family_;
+
+ /// @brief Expected DurationKey in Alarm after valid parsing.
+ DurationKeyPtr expected_key_;
+};
+
+/// @brief Test fixture for testing AlarmParser for DHCP(v4).
+class AlarmParserTest4: public AlarmParserTest {
+public:
+ /// @brief Constructor.
+ explicit AlarmParserTest4() : AlarmParserTest(AF_INET) {
+ expected_key_.reset(new DurationKey(family_, DHCPDISCOVER, DHCPOFFER,
+ "start_here", "stop_there", 33));
+ }
+
+ /// @brief Destructor.
+ virtual ~AlarmParserTest4() = default;
+
+ /// @brief Prepends json for a valid, DHCP 'duration-key' to json text.
+ ///
+ /// @param scenario_json text to prepend with the duration-key.
+ ///
+ /// @return string containing the prepended text.
+ virtual std::string makeValidKeyConfig(const std::string& scenario_json) {
+ std::stringstream oss;
+ oss << "{"
+ <<
+ R"( "duration-key": {
+ "query-type": "DHCPDISCOVER",
+ "response-type": "DHCPOFFER",
+ "start-event": "start_here",
+ "stop-event": "stop_there",
+ "subnet-id": 33
+ })"
+ << ","
+ << scenario_json << "}";
+
+ return (oss.str());
+ }
+};
+
+/// @brief Test fixture for testing AlarmParser for DHCPV6.
+class AlarmParserTest6: public AlarmParserTest {
+public:
+ /// @brief Constructor.
+ explicit AlarmParserTest6() : AlarmParserTest(AF_INET6) {
+ expected_key_.reset(new DurationKey(family_, DHCPV6_REQUEST, DHCPV6_REPLY,
+ "start_here", "stop_there", 33));
+ }
+
+ /// @brief Destructor.
+ virtual ~AlarmParserTest6() = default;
+
+ /// @brief Prepends json for a valid, DHCPV6 'duration-key' to json text.
+ ///
+ /// @param scenario_json text to prepend with the duration-key.
+ ///
+ /// @return string containing the prepended text.
+ virtual std::string makeValidKeyConfig(const std::string& scenario_json) {
+ std::stringstream oss;
+ oss << "{"
+ <<
+ R"( "duration-key": {
+ "query-type": "REQUEST",
+ "response-type": "REPLY",
+ "start-event": "start_here",
+ "stop-event": "stop_there",
+ "subnet-id": 33
+ })"
+ << ","
+ << scenario_json << "}";
+
+ return (oss.str());
+ }
+};
+
+TEST_F(AlarmParserTest4, validScenarios4) {
+ testValidScenarios();
+}
+
+TEST_F(AlarmParserTest4, invalidAlarmScenarios) {
+ testInvalidAlarmScenarios();
+}
+
+TEST_F(AlarmParserTest4, invalidDurationKey) {
+ // We test just enough key errors to ensure they're caught.
+ // List of test scenarios to run.
+ list<InvalidScenario> scenarios = {
+ {
+ // Missing duration-key element
+ __LINE__,
+ R"({
+ "enable-alarm" : true,
+ "high-water-ms" : 500,
+ "low-water-ms" : 25
+ })",
+ "'duration-key' parameter is required"
+ },
+ {
+ // Invalid type for duration-key.
+ __LINE__,
+ R"({
+ "duration-key": "not-a-map",
+ "enable-alarm" : true,
+ "high-water-ms" : 500,
+ "low-water-ms" : 25
+ })",
+ "'duration-key' parameter is not a map"
+ },
+ {
+ // Wrong messages type for v4.
+ __LINE__,
+ R"({
+ "duration-key": {
+ "query-type": "REQUEST",
+ "response-type": "REPLY",
+ "start-event" : "start-here",
+ "stop-event" : "stop-here"
+ },
+ "enable-alarm" : true,
+ "high-water-ms" : 500,
+ "low-water-ms" : 25
+ })",
+ "'query-type' parameter is invalid, 'REQUEST' is not a valid DHCP message type"
+ },
+ };
+
+ testInvalidScenarios(scenarios, false);
+}
+
+TEST_F(AlarmParserTest6, validScenarios) {
+ testValidScenarios();
+}
+
+TEST_F(AlarmParserTest6, invalidScenarios) {
+ testInvalidAlarmScenarios();
+}
+
+TEST_F(AlarmParserTest6, invalidDurationKey) {
+ // We test just enough key errors to ensure they're caught.
+ // List of test scenarios to run.
+ list<InvalidScenario> scenarios = {
+ {
+ // Missing duration-key element
+ __LINE__,
+ R"({
+ "enable-alarm" : true,
+ "high-water-ms" : 500,
+ "low-water-ms" : 25
+ })",
+ "'duration-key' parameter is required"
+ },
+ {
+ // Invalid type for duration-key.
+ __LINE__,
+ R"({
+ "duration-key": "not-a-map",
+ "enable-alarm" : true,
+ "high-water-ms" : 500,
+ "low-water-ms" : 25
+ })",
+ "'duration-key' parameter is not a map"
+ },
+ {
+ // Wrong messages type for v6.
+ __LINE__,
+ R"({
+ "duration-key": {
+ "query-type": "DHCPDISCOVER",
+ "response-type": "DHCPOFFER",
+ "start-event" : "start-here",
+ "stop-event" : "stop-here"
+ },
+ "enable-alarm" : true,
+ "high-water-ms" : 500,
+ "low-water-ms" : 25
+ })",
+ "'query-type' parameter is invalid, 'DHCPDISCOVER' is not a valid DHCPV6 message type"
+ },
+ };
+
+ testInvalidScenarios(scenarios, false);
+}
+
+} // end of anonymous namespace
--- /dev/null
+// Copyright (C) 2024 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/.
+
+/// @file This file contains tests which exercise the PerfmonConfig class.
+
+#include <config.h>
+#include <dhcp/dhcp6.h>
+#include <perfmon_config.h>
+#include <testutils/gtest_utils.h>
+
+#include <gtest/gtest.h>
+#include <list>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::perfmon;
+
+namespace {
+
+// These tests excerise DurationKeyParser which parses a map of
+// paramters as shown below:
+// "duration-key": {
+// "query-type" : "DHCPDISCOVER",
+// "response-type" : "DHCPOFFER",
+// "start-event" : "process-started",
+// "stop-event" : "process-completed",
+// "subnet-id" : 70
+// }
+
+/// @brief Describes a valid test scenario.
+struct ValidScenario {
+ int line_; // Scenario line number
+ std::string json_; // JSON configuration to parse
+ uint16_t exp_query_type_; // Expected value for query-type
+ uint16_t exp_response_type_; // Expected value for response-type
+ std::string exp_start_event_; // Expected value for start-event
+ std::string exp_stop_event_; // Expected value for stop-event
+ SubnetID exp_subnet_id_; // Expected value for subnet-id
+};
+
+/// @brief Describes an invalid test scenario.
+struct InvalidScenario {
+ int line_; // Scenario line number
+ std::string json_; // JSON configuration to parse
+ std::string exp_message_; // Expected error text
+};
+
+/// @brief Test fixture for testing DurationKeyParser.
+class DurationKeyParserTest: public ::testing::Test {
+public:
+ /// @brief Constructor.
+ DurationKeyParserTest() = default;
+
+ /// @brief Destructor.
+ virtual ~DurationKeyParserTest() = default;
+
+ /// @brief Runs a list of valid configurations through parsing.
+ ///
+ /// @param list of valid scenarios to run
+ /// @param family protocol family to use when parsing
+ void testValidScenarios(std::list<ValidScenario>& scenarios, uint16_t family) {
+ // Iterate over the scenarios.
+ for (auto const& scenario : scenarios) {
+ stringstream oss;
+ oss << "scenario at line: " << scenario.line_;
+ SCOPED_TRACE(oss.str());
+
+ // Convert JSON texts to Element map.
+ ConstElementPtr json_elements;
+ ASSERT_NO_THROW_LOG(json_elements = Element::fromJSON(scenario.json_));
+
+ // Parsing elements should succeed.
+ DurationKeyPtr key;
+ ASSERT_NO_THROW_LOG(key = DurationKeyParser::parse(json_elements, family));
+
+ // Verify expected values.
+ ASSERT_TRUE(key);
+ EXPECT_EQ(key->getQueryType(), scenario.exp_query_type_);
+ EXPECT_EQ(key->getResponseType(), scenario.exp_response_type_);
+ EXPECT_EQ(key->getStartEventLabel(), scenario.exp_start_event_);
+ EXPECT_EQ(key->getStopEventLabel(), scenario.exp_stop_event_);
+ EXPECT_EQ(key->getSubnetId(), scenario.exp_subnet_id_);
+ }
+ }
+
+ /// @brief Runs a list of invalid configurations through parsing.
+ ///
+ /// @param list of valid scenarios to run
+ /// @param family protocol family to use when parsing
+ void testInvalidScenarios(std::list<InvalidScenario>& scenarios, uint16_t family) {
+ // Iterate over the scenarios.
+ for (auto const& scenario : scenarios) {
+ stringstream oss;
+ oss << "scenario at line: " << scenario.line_;
+ SCOPED_TRACE(oss.str());
+
+ // Convert JSON texts to Element map.
+ ConstElementPtr json_elements;
+ ASSERT_NO_THROW_LOG(json_elements = Element::fromJSON(scenario.json_));
+
+ // Parsing elements should succeed.
+ ASSERT_THROW_MSG(DurationKeyParser::parse(json_elements, family), DhcpConfigError,
+ scenario.exp_message_);
+ }
+ }
+};
+
+TEST_F(DurationKeyParserTest, validScenarios4) {
+ // List of test scenarios to run.
+ std::list<ValidScenario> scenarios = {
+ {
+ // All parameters,
+ __LINE__,
+ R"(
+ {
+ "query-type": "DHCPDISCOVER",
+ "response-type": "DHCPOFFER",
+ "start-event": "start_here",
+ "stop-event": "stop_there",
+ "subnet-id": 700
+ })",
+ DHCPDISCOVER, DHCPOFFER, "start_here", "stop_there", 700
+ },
+ {
+ // Empty message types - we allow empty types for API lookups
+ __LINE__,
+ R"(
+ {
+ "query-type": "",
+ "response-type": "",
+ "start-event": "start_here",
+ "stop-event": "stop_there",
+ "subnet-id": 700
+ })",
+ DHCP_NOTYPE, DHCP_NOTYPE, "start_here", "stop_there", 700
+ },
+ {
+ // Empty event labels - we allow empty events for API lookups
+ __LINE__,
+ R"(
+ {
+ "query-type": "DHCPDISCOVER",
+ "response-type": "DHCPOFFER",
+ "start-event": "",
+ "stop-event": "",
+ "subnet-id": 700
+ })",
+ DHCPDISCOVER, DHCPOFFER, "", "", 700
+ },
+ {
+ // Subnet id zero,
+ __LINE__,
+ R"(
+ {
+ "query-type": "DHCPDISCOVER",
+ "response-type": "DHCPOFFER",
+ "start-event": "start_here",
+ "stop-event": "stop_there",
+ "subnet-id": 0
+ })",
+ DHCPDISCOVER, DHCPOFFER, "start_here", "stop_there", SUBNET_ID_GLOBAL
+ },
+ };
+
+ testValidScenarios(scenarios, AF_INET);
+}
+
+TEST_F(DurationKeyParserTest, invalidScenarios4) {
+ // List of test scenarios to run.
+ list<InvalidScenario> scenarios = {
+ {
+ // Spurious parameter
+ __LINE__,
+ R"(
+ {
+ "query-type": "DHCPDISCOVER",
+ "response-type": "DHCPOFFER",
+ "start-event": "start_here",
+ "stop-event": "stop_there",
+ "subnet-id": 700,
+ "bogus": true
+ })",
+ "spurious 'bogus' parameter"
+ },
+ {
+ // Missing query-type
+ __LINE__,
+ R"(
+ {
+ "response-type": "DHCPOFFER",
+ "start-event": "start_here",
+ "stop-event": "stop_there",
+ "subnet-id": 700
+ })",
+ "'query-type' parameter is required"
+ },
+ {
+ // Non-string value for query-type
+ __LINE__,
+ R"(
+ {
+ "query-type": 1234,
+ "response-type": "DHCPOFFER",
+ "start-event": "start_here",
+ "stop-event": "stop_there",
+ "subnet-id": 700
+ })",
+ "'query-type' parameter is not a string"
+ },
+ {
+ // Non-existent query-type
+ __LINE__,
+ R"(
+ {
+ "query-type": "BOGUS",
+ "response-type": "DHCPOFFER",
+ "start-event": "start_here",
+ "stop-event": "stop_there",
+ "subnet-id": 700
+ })",
+ "'query-type' parameter is invalid, 'BOGUS' is not a valid DHCP message type"
+ },
+ {
+ // Missing response-type
+ __LINE__,
+ R"(
+ {
+ "query-type": "DHCPDISCOVER",
+ "start-event": "start_here",
+ "stop-event": "stop_there",
+ "subnet-id": 700
+ })",
+ "'response-type' parameter is required"
+ },
+ {
+ // Non-string value for response-type
+ __LINE__,
+ R"(
+ {
+ "query-type": "DHCDISCOVER",
+ "response-type": 5768,
+ "start-event": "start_here",
+ "stop-event": "stop_there",
+ "subnet-id": 700
+ })",
+ "'response-type' parameter is not a string"
+ },
+ {
+ // Non-existent response-type
+ __LINE__,
+ R"(
+ {
+ "query-type": "DHCPDISCOVER",
+ "response-type": "BOGUS",
+ "start-event": "start_here",
+ "stop-event": "stop_there",
+ "subnet-id": 700
+ })",
+ "'response-type' parameter is invalid, 'BOGUS' is not a valid DHCP message type"
+ },
+ {
+ // Missing start-event
+ __LINE__,
+ R"(
+ {
+ "query-type": "DHCPDISCOVER",
+ "response-type": "DHCPOFFER",
+ "stop-event": "stop_there",
+ "subnet-id": 700
+ })",
+ "'start-event' parameter is required"
+ },
+ {
+ // Non-string start-event
+ __LINE__,
+ R"(
+ {
+ "query-type": "DHCPDISCOVER",
+ "response-type": "DHCPOFFER",
+ "start-event": 5678,
+ "stop-event": "stop_there",
+ "subnet-id": 700
+ })",
+ "'start-event' parameter is not a string"
+ },
+ {
+ // Missing stop-event
+ __LINE__,
+ R"(
+ {
+ "query-type": "DHCPDISCOVER",
+ "response-type": "DHCPOFFER",
+ "start-event": "start_here",
+ "subnet-id": 700
+ })",
+ "'stop-event' parameter is required"
+ },
+ {
+ // Non-string start-event
+ __LINE__,
+ R"(
+ {
+ "query-type": "DHCPDISCOVER",
+ "response-type": "DHCPOFFER",
+ "start-event": "start_here",
+ "stop-event": 1234,
+ "subnet-id": 700
+ })",
+ "'stop-event' parameter is not a string"
+ },
+ {
+ // Non-integer subnet-id
+ __LINE__,
+ R"(
+ {
+ "query-type": "DHCPDISCOVER",
+ "response-type": "DHCPOFFER",
+ "start-event": "start_here",
+ "stop-event": "stop_here",
+ "subnet-id": false
+ })",
+ "'subnet-id' parameter is not an integer"
+ },
+ };
+
+ testInvalidScenarios(scenarios, AF_INET);
+}
+
+TEST_F(DurationKeyParserTest, parseValidScenarios6) {
+ // List of test scenarios to run.
+ std::list<ValidScenario> scenarios = {
+ {
+ // All parameters,
+ __LINE__,
+ R"(
+ {
+ "query-type": "SOLICIT",
+ "response-type": "ADVERTISE",
+ "start-event": "start_here",
+ "stop-event": "stop_there",
+ "subnet-id": 700
+ })",
+ DHCPV6_SOLICIT, DHCPV6_ADVERTISE, "start_here", "stop_there", 700
+ },
+ {
+ // Empty message types - we allow empty types for API lookups
+ __LINE__,
+ R"(
+ {
+ "query-type": "",
+ "response-type": "",
+ "start-event": "start_here",
+ "stop-event": "stop_there",
+ "subnet-id": 700
+ })",
+ DHCP_NOTYPE, DHCP_NOTYPE, "start_here", "stop_there", 700
+ },
+ {
+ // Empty event labels - we allow empty events for API lookups
+ __LINE__,
+ R"(
+ {
+ "query-type": "SOLICIT",
+ "response-type": "ADVERTISE",
+ "start-event": "",
+ "stop-event": "",
+ "subnet-id": 700
+ })",
+ DHCPV6_SOLICIT, DHCPV6_ADVERTISE, "", "", 700
+ },
+ {
+ // Subnet id zero,
+ __LINE__,
+ R"(
+ {
+ "query-type": "SOLICIT",
+ "response-type": "ADVERTISE",
+ "start-event": "start_here",
+ "stop-event": "stop_there",
+ "subnet-id": 0
+ })",
+ DHCPV6_SOLICIT, DHCPV6_ADVERTISE, "start_here", "stop_there", SUBNET_ID_GLOBAL
+ },
+ };
+
+ testValidScenarios(scenarios, AF_INET6);
+}
+
+TEST_F(DurationKeyParserTest, invalidScenarios6) {
+ // List of test scenarios to run.
+ list<InvalidScenario> scenarios = {
+ {
+ // Spurious parameter
+ __LINE__,
+ R"(
+ {
+ "query-type": "SOLICIT",
+ "response-type": "ADVERTISE",
+ "start-event": "start_here",
+ "stop-event": "stop_there",
+ "subnet-id": 700,
+ "bogus": true
+ })",
+ "spurious 'bogus' parameter"
+ },
+ {
+ // Missing query-type
+ __LINE__,
+ R"(
+ {
+ "response-type": "ADVERTISE",
+ "start-event": "start_here",
+ "stop-event": "stop_there",
+ "subnet-id": 700
+ })",
+ "'query-type' parameter is required"
+ },
+ {
+ // Non-string value for query-type
+ __LINE__,
+ R"(
+ {
+ "query-type": 1234,
+ "response-type": "ADVERTISE",
+ "start-event": "start_here",
+ "stop-event": "stop_there",
+ "subnet-id": 700
+ })",
+ "'query-type' parameter is not a string"
+ },
+ {
+ // Non-existent query-type
+ __LINE__,
+ R"(
+ {
+ "query-type": "BOGUS",
+ "response-type": "ADVERTISE",
+ "start-event": "start_here",
+ "stop-event": "stop_there",
+ "subnet-id": 700
+ })",
+ "'query-type' parameter is invalid, 'BOGUS' is not a valid DHCPV6 message type"
+ },
+ {
+ // Missing response-type
+ __LINE__,
+ R"(
+ {
+ "query-type": "SOLICIT",
+ "start-event": "start_here",
+ "stop-event": "stop_there",
+ "subnet-id": 700
+ })",
+ "'response-type' parameter is required"
+ },
+ {
+ // Non-string value for response-type
+ __LINE__,
+ R"(
+ {
+ "query-type": "DHCDISCOVER",
+ "response-type": 5768,
+ "start-event": "start_here",
+ "stop-event": "stop_there",
+ "subnet-id": 700
+ })",
+ "'response-type' parameter is not a string"
+ },
+ {
+ // Non-existent response-type
+ __LINE__,
+ R"(
+ {
+ "query-type": "SOLICIT",
+ "response-type": "BOGUS",
+ "start-event": "start_here",
+ "stop-event": "stop_there",
+ "subnet-id": 700
+ })",
+ "'response-type' parameter is invalid, 'BOGUS' is not a valid DHCPV6 message type"
+ },
+ {
+ // Missing start-event
+ __LINE__,
+ R"(
+ {
+ "query-type": "SOLICIT",
+ "response-type": "ADVERTISE",
+ "stop-event": "stop_there",
+ "subnet-id": 700
+ })",
+ "'start-event' parameter is required"
+ },
+ {
+ // Non-string start-event
+ __LINE__,
+ R"(
+ {
+ "query-type": "SOLICIT",
+ "response-type": "ADVERTISE",
+ "start-event": 5678,
+ "stop-event": "stop_there",
+ "subnet-id": 700
+ })",
+ "'start-event' parameter is not a string"
+ },
+ {
+ // Missing stop-event
+ __LINE__,
+ R"(
+ {
+ "query-type": "SOLICIT",
+ "response-type": "ADVERTISE",
+ "start-event": "start_here",
+ "subnet-id": 700
+ })",
+ "'stop-event' parameter is required"
+ },
+ {
+ // Non-string start-event
+ __LINE__,
+ R"(
+ {
+ "query-type": "SOLICIT",
+ "response-type": "ADVERTISE",
+ "start-event": "start_here",
+ "stop-event": 1234,
+ "subnet-id": 700
+ })",
+ "'stop-event' parameter is not a string"
+ },
+ {
+ // Non-integer subnet-id
+ __LINE__,
+ R"(
+ {
+ "query-type": "SOLICIT",
+ "response-type": "ADVERTISE",
+ "start-event": "start_here",
+ "stop-event": "stop_here",
+ "subnet-id": false
+ })",
+ "'subnet-id' parameter is not an integer"
+ },
+ };
+
+ testInvalidScenarios(scenarios, AF_INET6);
+}
+
+} // end of anonymous namespace
--- /dev/null
+// Copyright (C) 2024 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/.
+
+/// @file This file contains tests which exercise the PerfMonConfig class.
+
+#include <config.h>
+#include <perfmon_config.h>
+#include <dhcp/dhcp6.h>
+#include <testutils/gtest_utils.h>
+
+#include <gtest/gtest.h>
+#include <list>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::perfmon;
+
+namespace {
+
+/// @brief Test fixture for testing PerfMonConfig parsing of the
+/// hook library's 'parameters' element.
+class PerfMonConfigTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ explicit PerfMonConfigTest(uint16_t family) : family_(family) {
+ }
+
+ /// @brief Destructor.
+ virtual ~PerfMonConfigTest() = default;
+
+ /// @brief Verifies PerfMonConfig constructors and accessors.
+ void testBasics() {
+ PerfMonConfigPtr config;
+
+ // Verify that an invalid family is caught.
+ ASSERT_THROW_MSG(config.reset(new PerfMonConfig(777)), BadValue,
+ "PerfmonConfig: family must be AF_INET or AF_INET6");
+
+ // Verify initial values.
+ ASSERT_NO_THROW_LOG(config.reset(new PerfMonConfig(family_)));
+ ASSERT_TRUE(config);
+ EXPECT_TRUE(config->getEnableMonitoring());
+ EXPECT_EQ(config->getIntervalWidthSecs(), 60);
+ EXPECT_TRUE(config->getStatsMgrReporting());
+ EXPECT_EQ(config->getAlarmReportSecs(), 300);
+ EXPECT_TRUE(config->getAlarmStore());
+
+ // Verify accessors.
+ EXPECT_NO_THROW_LOG(config->setEnableMonitoring(false));
+ EXPECT_FALSE(config->getEnableMonitoring());
+
+ EXPECT_NO_THROW_LOG(config->setIntervalWidthSecs(4));
+ EXPECT_EQ(config->getIntervalWidthSecs(), 4);
+
+ EXPECT_NO_THROW_LOG(config->setStatsMgrReporting(false));
+ EXPECT_FALSE(config->getStatsMgrReporting());
+
+ EXPECT_NO_THROW_LOG(config->setAlarmReportSecs(120));
+ EXPECT_EQ(config->getAlarmReportSecs(), 120);
+
+ // Verify shallow copy construction.
+ PerfMonConfigPtr config2(new PerfMonConfig(*config));
+ EXPECT_FALSE(config2->getEnableMonitoring());
+ EXPECT_EQ(config2->getIntervalWidthSecs(), 4);
+ EXPECT_FALSE(config2->getStatsMgrReporting());
+ EXPECT_EQ(config2->getAlarmReportSecs(), 120);
+ EXPECT_EQ(config2->getAlarmStore(), config->getAlarmStore());
+ }
+
+ /// @brief Exercises PerfMonConfig parameter parsing with valid configuration
+ /// permutations.
+ /// @todo add alarms
+ void testValidScenarios() {
+ // Describes a test scenario.
+ struct Scenario {
+ int line_; // Scenario line number
+ std::string json_; // JSON configuration to parse
+ bool exp_enable_monitoring_; // Expected value for enable-monitoring
+ uint32_t exp_interval_width_secs_; // Expected value for interval-width-secs
+ bool exp_stats_mgr_reporting_; // Expected value for stats-mgr-reporting
+ uint32_t exp_alarm_report_secs_; // Expected value for alarm-report-secs
+ };
+
+ // List of test scenarios to run.
+ list<Scenario> scenarios = {
+ {
+ // Empty map
+ __LINE__,
+ R"({ })",
+ true, 60, true, 300
+ },
+ {
+ // Only enable-monitoring",
+ __LINE__,
+ R"({ "enable-monitoring" : false })",
+ false, 60, true, 300
+ },
+ {
+ // Only interval-width-secs",
+ __LINE__,
+ R"({ "interval-width-secs" : 3 })",
+ true, 3, true, 300
+ },
+ {
+ // Only stats-mgr-reporting",
+ __LINE__,
+ R"({ "stats-mgr-reporting" : false })",
+ true, 60, false, 300
+ },
+ {
+ // Only alarm-report-secs",
+ __LINE__,
+ R"({ "alarm-report-secs" : 77 })",
+ true, 60, true, 77
+ },
+ {
+ // All parameters",
+ __LINE__,
+ R"(
+ {
+ "enable-monitoring" : false,
+ "interval-width-secs" : 2,
+ "stats-mgr-reporting" : false,
+ "alarm-report-secs" : 120
+ })",
+ false, 2, false, 120
+ },
+ };
+
+ // Iterate over the scenarios.
+ for (auto const& scenario : scenarios) {
+ stringstream oss;
+ oss << "scenario at line: " << scenario.line_;
+ SCOPED_TRACE(oss.str());
+
+ // Convert JSON texts to Element map.
+ ConstElementPtr json_elements;
+ ASSERT_NO_THROW_LOG(json_elements = Element::fromJSON(scenario.json_));
+
+ // Parsing elements should succeed.
+ PerfMonConfig config(family_);
+ ASSERT_NO_THROW_LOG(config.parse(json_elements));
+
+ // Verify expected values.
+ EXPECT_EQ(config.getEnableMonitoring(), scenario.exp_enable_monitoring_);
+ EXPECT_EQ(config.getIntervalWidthSecs(), scenario.exp_interval_width_secs_);
+ EXPECT_EQ(config.getStatsMgrReporting(), scenario.exp_stats_mgr_reporting_);
+ EXPECT_EQ(config.getAlarmReportSecs(), scenario.exp_alarm_report_secs_);
+ }
+ }
+
+ /// @brief Exercises PerfMonConfig parameter parsing with invalid configuration
+ /// permutations. Duplicate alarms are tested elsewhere.
+ void testInvalidScenarios() {
+ // Describes a test scenario.
+ struct Scenario {
+ int line_; // Scenario line number
+ string json_; // JSON configuration to parse
+ string exp_message_; // Expected exception message
+ };
+
+ // List of test scenarios to run. Most scenario supply
+ // all valid parameters except one in error. This allows
+ // us to verify that no values are changed if any are in error.
+ list<Scenario> scenarios = {
+ {
+ // Unknown parameter
+ __LINE__,
+ R"(
+ {
+ "enable-monitoring" : false,
+ "interval-width-secs" : 3,
+ "stats-mgr-reporting" : false,
+ "alarm-report-secs" : 90,
+ "bogus" : false
+ })",
+ "spurious 'bogus' parameter"
+ },
+ {
+ // Invalid type for enable-monitoring
+ __LINE__,
+ R"(
+ {
+ "enable-monitoring" : "not bool",
+ "interval-width-secs" : 3,
+ "stats-mgr-reporting" : false,
+ "alarm-report-secs" : 90
+ })",
+ "'enable-monitoring' parameter is not a boolean"
+ },
+ {
+ // Value of interval-width-secs is zero
+ __LINE__,
+ R"(
+ {
+ "enable-monitoring" : false,
+ "interval-width-secs" : 0,
+ "stats-mgr-reporting" : false,
+ "alarm-report-secs" : 90
+ })",
+ "invalid interval-width-secs: '0', must be greater than 0"
+ },
+ {
+ // Value of interval-width-secs less than zero
+ __LINE__,
+ R"(
+ {
+ "enable-monitoring" : false,
+ "interval-width-secs" : -2,
+ "stats-mgr-reporting" : false,
+ "alarm-report-secs" : 90
+ })",
+ "invalid interval-width-secs: '-2', must be greater than 0"
+ },
+ {
+ // Non-boolean type for stats-mgr-reporting
+ __LINE__,
+ R"(
+ {
+ "enable-monitoring" : false,
+ "interval-width-secs" : 1,
+ "stats-mgr-reporting" : "not bool",
+ "alarm-report-secs" : 90
+ })",
+ "'stats-mgr-reporting' parameter is not a boolean"
+ },
+ {
+ // Value of alarm-report-secs is zero
+ __LINE__,
+ R"(
+ {
+ "enable-monitoring" : false,
+ "interval-width-secs" : 1,
+ "stats-mgr-reporting" : false,
+ "alarm-report-secs" : -3
+ })",
+ "invalid alarm-report-secs: '-3', cannot be less than 0"
+ },
+ {
+ // Value of alarm-report-secs less than zero
+ __LINE__,
+ R"(
+ {
+ "enable-monitoring" : false,
+ "interval-width-secs" : 1,
+ "stats-mgr-reporting" : false,
+ "alarm-report-secs" : -3
+ })",
+ "invalid alarm-report-secs: '-3', cannot be less than 0"
+ },
+ {
+ // Value for alarms is not a list.
+ __LINE__,
+ R"(
+ {
+ "enable-monitoring" : false,
+ "interval-width-secs" : 60,
+ "stats-mgr-reporting" : false,
+ "alarm-report-secs" : 90,
+ "alarms": {}
+ })",
+ "'alarms' parameter is not a list"
+ },
+ {
+ // Alarms list contains an invalid entry
+ __LINE__,
+ R"(
+ {
+ "enable-monitoring" : false,
+ "interval-width-secs" : 60,
+ "stats-mgr-reporting" : false,
+ "alarm-report-secs" : 90,
+ "alarms": [{ "bogus": "alarm" }]
+ })",
+ "cannot add Alarm to store: spurious 'bogus' parameter"
+ }
+ };
+
+ // Iterate over the scenarios.
+ PerfMonConfig default_config(family_);
+ for (auto const& scenario : scenarios) {
+ stringstream oss;
+ oss << "scenario at line: " << scenario.line_;
+ SCOPED_TRACE(oss.str());
+
+ // Convert JSON text to a map of parameters.
+ ConstElementPtr json_elements;
+ ASSERT_NO_THROW_LOG(json_elements = Element::fromJSON(scenario.json_));
+
+ // Parsing parameters should throw.
+ PerfMonConfig config(family_);
+ ASSERT_THROW_MSG(config.parse(json_elements), DhcpConfigError,
+ scenario.exp_message_);
+
+ // Original values should be intact.
+ EXPECT_EQ(default_config.getEnableMonitoring(), config.getEnableMonitoring());
+ EXPECT_EQ(default_config.getIntervalWidthSecs(), config.getIntervalWidthSecs());
+ EXPECT_EQ(default_config.getStatsMgrReporting(), config.getStatsMgrReporting());
+ EXPECT_EQ(default_config.getAlarmReportSecs(), config.getAlarmReportSecs());
+ }
+ }
+
+ /// @brief Creates a valid configuration with a list of alarms.
+ ///
+ /// @parameter keys list of DurationKeyPtrs for alarms that should appear
+ /// in the list.
+ ///
+ /// @return JSON text for the configuration.
+ std::string makeConfigWithAlarms(std::vector<DurationKeyPtr> keys) {
+ // Create valid configuration test which includes an arbitrary number of
+ // family-specific alarms from a set of DurationKeys.
+ stringstream joss;
+ joss << R"(
+ {
+ "enable-monitoring" : false,
+ "interval-width-secs" : 60,
+ "stats-mgr-reporting" : false,
+ "alarm-report-secs" : 90,
+ "alarms": [
+ )";
+
+ std::string comma="";
+ for (auto const& key : keys) {
+ joss << comma << "\t{";
+ joss << R"("duration-key": )";
+ auto key_elems = DurationKeyParser::toElement(key);
+ key_elems->toJSON(joss);
+ joss << R"(,
+ "high-water-ms": 500,
+ "low-water-ms": 25
+ }
+ )";
+
+ comma = ",";
+ }
+
+ joss << "]}";
+ return (joss.str());
+ }
+
+ /// @brief Verifies a valid configuration that includes a list of Alarms.
+ void testValidAlarmsList() {
+ // Create valid configuration test which includes an arbitrary number of
+ // family-specific alarms from a pre-defined set of unique DurationKeys.
+ std::string json_text = makeConfigWithAlarms(keys_);
+
+ // Convert JSON text to a map of parameters.
+ ConstElementPtr json_elements;
+ ASSERT_NO_THROW_LOG(json_elements = Element::fromJSON(json_text));
+
+ // Parsing parameters should throw.
+ PerfMonConfig config(family_);
+ ASSERT_NO_THROW_LOG(config.parse(json_elements));
+
+ // Get all should retrieve the alarms in ascending order.
+ AlarmCollectionPtr alarms = config.getAlarmStore()->getAll();
+ ASSERT_EQ(alarms->size(), keys_.size());
+
+ int idx = 0;
+ for (auto const& d : *alarms) {
+ EXPECT_EQ(*d, *keys_[idx]) << "failed on pass :" << idx;
+ ++idx;
+ }
+ }
+
+ /// @brief Verifies a valid configuration with a list duplicate Alarms.
+ void testDuplicateAlarms() {
+ std::vector<DurationKeyPtr> duplicate_keys;
+ duplicate_keys.push_back(keys_[0]);
+ duplicate_keys.push_back(keys_[0]);
+
+ // Create valid configuration test which includes an arbitrary number of
+ // family-specific alarms from a pre-defined set of unique DurationKeys.
+ std::string json_text = makeConfigWithAlarms(duplicate_keys);
+
+ // Convert JSON text to a map of parameters.
+ ConstElementPtr json_elements;
+ ASSERT_NO_THROW_LOG(json_elements = Element::fromJSON(json_text));
+
+ // Parsing parameters should throw.
+ PerfMonConfig config(family_);
+ if (family_ == AF_INET) {
+ ASSERT_THROW_MSG(config.parse(json_elements), DhcpConfigError,
+ "cannot add Alarm to store: AlarmStore::addAlarm:"
+ " alarm already exists for:"
+ " DHCPDISCOVER-DHCPOFFER.socket_received-buffer_read.0");
+ } else {
+ ASSERT_THROW_MSG(config.parse(json_elements), DhcpConfigError,
+ "cannot add Alarm to store: AlarmStore::addAlarm:"
+ " alarm already exists for:"
+ " SOLICIT-REPLY.socket_received-buffer_read.0");
+ }
+ }
+
+ /// @brief Protocol family AF_INET or AF_INET6
+ uint16_t family_;
+
+ /// @brief Collection of valid family-specific keys.
+ std::vector<DurationKeyPtr> keys_;
+};
+
+/// @brief Test fixture for testing PerfMonConfig for DHCP(v4).
+class PerfMonConfigTest4: public PerfMonConfigTest {
+public:
+ /// @brief Constructor.
+ explicit PerfMonConfigTest4() : PerfMonConfigTest(AF_INET) {
+ for (int subnet = 0; subnet < 3; ++subnet) {
+ DurationKeyPtr key(new DurationKey(AF_INET, DHCPDISCOVER, DHCPOFFER,
+ "socket_received", "buffer_read", subnet));
+ keys_.push_back(key);
+ }
+ }
+
+ /// @brief Destructor.
+ virtual ~PerfMonConfigTest4() = default;
+};
+
+/// @brief Test fixture for testing PerfMonConfig for DHCPV6.
+class PerfMonConfigTest6: public PerfMonConfigTest {
+public:
+ /// @brief Constructor.
+ explicit PerfMonConfigTest6() : PerfMonConfigTest(AF_INET6) {
+ for (int subnet = 0; subnet < 3; ++subnet) {
+ DurationKeyPtr key(new DurationKey(AF_INET6, DHCPV6_SOLICIT, DHCPV6_REPLY,
+ "socket_received", "buffer_read", subnet));
+ keys_.push_back(key);
+ }
+ }
+
+ /// @brief Destructor.
+ virtual ~PerfMonConfigTest6() = default;
+};
+
+TEST_F(PerfMonConfigTest4, basics) {
+ testBasics();
+}
+
+TEST_F(PerfMonConfigTest6, basics) {
+ testBasics();
+}
+
+TEST_F(PerfMonConfigTest4, validScenarios) {
+ testValidScenarios();
+}
+
+TEST_F(PerfMonConfigTest6, validScenarios) {
+ testValidScenarios();
+}
+
+TEST_F(PerfMonConfigTest4, invalidScenarios) {
+ testInvalidScenarios();
+}
+
+TEST_F(PerfMonConfigTest6, invalidScenarios) {
+ testInvalidScenarios();
+}
+
+TEST_F(PerfMonConfigTest4, validAlarmsList) {
+ testValidAlarmsList();
+}
+
+TEST_F(PerfMonConfigTest6, validAlarmsList) {
+ testValidAlarmsList();
+}
+
+TEST_F(PerfMonConfigTest4, duplicateAlarms) {
+ testDuplicateAlarms();
+}
+
+TEST_F(PerfMonConfigTest6, duplicateAlarms) {
+ testDuplicateAlarms();
+}
+
+} // end of anonymous namespace
--- /dev/null
+// Copyright (C) 2024 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/.
+
+/// @file This file contains tests which exercise the PerfmonMgr class.
+#include <config.h>
+#include <perfmon_mgr.h>
+#include <dhcp/dhcp6.h>
+#include <testutils/gtest_utils.h>
+#include <testutils/multi_threading_utils.h>
+
+#include <gtest/gtest.h>
+#include <sstream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::perfmon;
+using namespace isc::test;
+using namespace boost::posix_time;
+
+namespace {
+
+// Verifies MonitoredDurationStore valid construction.
+TEST(PerfMonMgr, constructor) {
+ PerfMonMgrPtr mgr;
+
+ EXPECT_NO_THROW_LOG(mgr.reset(new PerfMonMgr(AF_INET)));
+ ASSERT_TRUE(mgr);
+ EXPECT_EQ(mgr->getFamily(), AF_INET);
+
+ EXPECT_NO_THROW_LOG(mgr.reset(new PerfMonMgr(AF_INET6)));
+ ASSERT_TRUE(mgr);
+ EXPECT_EQ(mgr->getFamily(), AF_INET6);
+}
+
+/// @brief Test fixture for testing PerfMonMgr
+class PerfMonMgrTest : public ::testing::Test {
+public:
+ /// @brief Constructor.
+ explicit PerfMonMgrTest(uint16_t family) : family_(family) {
+ }
+
+ /// @brief Destructor.
+ virtual ~PerfMonMgrTest() = default;
+
+ /// @brief Verifies PerfMonConfig constructors and accessors.
+ void testBasics() {
+ PerfMonMgrPtr mgr;
+
+ // Verify that an invalid family is caught.
+ ASSERT_THROW_MSG(mgr.reset(new PerfMonMgr(777)), BadValue,
+ "PerfmonConfig: family must be AF_INET or AF_INET6");
+
+ // Verify initial values.
+ ASSERT_NO_THROW_LOG(mgr.reset(new PerfMonMgr(family_)));
+ ASSERT_TRUE(mgr);
+ EXPECT_TRUE(mgr->getEnableMonitoring());
+ EXPECT_EQ(mgr->getIntervalDuration(), seconds(60));
+ EXPECT_TRUE(mgr->getStatsMgrReporting());
+ EXPECT_EQ(mgr->getAlarmReportInterval(), seconds(300));
+
+ // Alarm store should exist but be empty.
+ EXPECT_TRUE(mgr->getAlarmStore());
+ EXPECT_EQ(mgr->getAlarmStore()->getFamily(), family_);
+ AlarmCollectionPtr alarms = mgr->getAlarmStore()->getAll();
+ ASSERT_EQ(alarms->size(), 0);
+
+ // Duration store should exist but be empty.
+ EXPECT_TRUE(mgr->getDurationStore());
+ EXPECT_EQ(mgr->getDurationStore()->getFamily(), family_);
+ MonitoredDurationCollectionPtr durations = mgr->getDurationStore()->getAll();
+ ASSERT_EQ(durations->size(), 0);
+ }
+
+ /// @brief Exercises PerfMonConfig parameter parsing with valid configuration
+ /// permutations.
+ /// @todo add alarms
+ void testValidConfig() {
+ std::string valid_config =
+ R"({
+ "enable-monitoring" : false,
+ "interval-width-secs" : 5,
+ "stats-mgr-reporting" : false,
+ "alarm-report-secs" : 600,
+ "alarms": [{
+ "duration-key": {
+ "query-type" : "",
+ "response-type" : "",
+ "start-event" : "process-started",
+ "stop-event" : "process-completed",
+ "subnet-id" : 70
+ },
+ "enable-alarm" : true,
+ "high-water-ms" : 500,
+ "low-water-ms" : 25
+ }]
+ })";
+
+ // Convert JSON texts to Element map.
+ ConstElementPtr json_elements;
+ ASSERT_NO_THROW_LOG(json_elements = Element::fromJSON(valid_config));
+
+ PerfMonMgrPtr mgr(new PerfMonMgr(family_));
+ ASSERT_NO_THROW_LOG(mgr->configure(json_elements));
+
+ EXPECT_FALSE(mgr->getEnableMonitoring());
+ EXPECT_EQ(mgr->getIntervalDuration(), seconds(5));
+ EXPECT_FALSE(mgr->getStatsMgrReporting());
+ EXPECT_EQ(mgr->getAlarmReportInterval(), seconds(600));
+
+ // AlarmStore should have one alarm.
+ EXPECT_TRUE(mgr->getAlarmStore());
+ EXPECT_EQ(mgr->getAlarmStore()->getFamily(), family_);
+ AlarmCollectionPtr alarms = mgr->getAlarmStore()->getAll();
+ ASSERT_EQ(alarms->size(), 1);
+ DurationKeyPtr key(new DurationKey(family_, 0, 0, "process-started", "process-completed", 70));
+ AlarmPtr alarm = (*alarms)[0];
+ ASSERT_TRUE(alarm);
+ EXPECT_EQ(*alarm, *key) << "alarm:" << alarm->getLabel();
+ EXPECT_EQ(alarm->getState(), Alarm::CLEAR);
+ EXPECT_EQ(alarm->getHighWater(), milliseconds(500));
+ EXPECT_EQ(alarm->getLowWater(), milliseconds(25));
+
+ // Duration store should exist but be empty.
+ EXPECT_TRUE(mgr->getDurationStore());
+ EXPECT_EQ(mgr->getDurationStore()->getFamily(), family_);
+ MonitoredDurationCollectionPtr durations = mgr->getDurationStore()->getAll();
+ ASSERT_EQ(durations->size(), 0);
+ }
+
+ /// @brief Exercises PerfMonConfig parameter parsing with valid configuration
+ /// permutations.
+ /// @todo add alarms
+ void testInvalidConfig() {
+ std::string valid_config =
+ R"({
+ "enable-monitoring" : false,
+ "interval-width-secs" : 5,
+ "stats-mgr-reporting" : false,
+ "alarm-report-secs" : 600,
+ "alarms": "bogus"
+ })";
+
+ // Convert JSON texts to Element map.
+ ConstElementPtr json_elements;
+ ASSERT_NO_THROW_LOG(json_elements = Element::fromJSON(valid_config));
+
+ PerfMonMgrPtr mgr(new PerfMonMgr(family_));
+ ASSERT_NO_THROW_LOG(mgr->configure(json_elements));
+
+ EXPECT_FALSE(mgr->getEnableMonitoring());
+ EXPECT_EQ(mgr->getIntervalDuration(), seconds(5));
+ EXPECT_FALSE(mgr->getStatsMgrReporting());
+ EXPECT_EQ(mgr->getAlarmReportInterval(), seconds(600));
+
+ // Alarm store should exist but be empty.
+ EXPECT_TRUE(mgr->getAlarmStore());
+ EXPECT_EQ(mgr->getAlarmStore()->getFamily(), family_);
+ AlarmCollectionPtr alarms = mgr->getAlarmStore()->getAll();
+ ASSERT_EQ(alarms->size(), 0);
+
+ // Duration store should exist but be empty.
+ EXPECT_TRUE(mgr->getDurationStore());
+ EXPECT_EQ(mgr->getDurationStore()->getFamily(), family_);
+ MonitoredDurationCollectionPtr durations = mgr->getDurationStore()->getAll();
+ ASSERT_EQ(durations->size(), 0);
+ }
+
+ /// @brief Protocol family AF_INET or AF_INET6
+ uint16_t family_;
+};
+
+/// @brief Test fixture for testing PerfMonConfig for DHCP(v4).
+class PerfMonMgrTest4: public PerfMonMgrTest {
+public:
+ /// @brief Constructor.
+ explicit PerfMonMgrTest4() : PerfMonMgrTest(AF_INET) {
+ }
+
+ /// @brief Destructor.
+ virtual ~PerfMonMgrTest4() = default;
+};
+
+/// @brief Test fixture for testing PerfMonConfig for DHCPV6.
+class PerfMonMgrTest6: public PerfMonMgrTest {
+public:
+ /// @brief Constructor.
+ explicit PerfMonMgrTest6() : PerfMonMgrTest(AF_INET6) {
+ }
+
+ /// @brief Destructor.
+ virtual ~PerfMonMgrTest6() = default;
+};
+
+TEST_F(PerfMonMgrTest4, basics) {
+ testBasics();
+}
+
+TEST_F(PerfMonMgrTest6, basics) {
+ testBasics();
+}
+
+TEST_F(PerfMonMgrTest4, validConfig) {
+ testValidConfig();
+}
+
+TEST_F(PerfMonMgrTest6, validConfig) {
+ testValidConfig();
+}
+
+} // end of anonymous namespace