diff --git a/doc/sphinx/arm/dhcp4-srv.rst b/doc/sphinx/arm/dhcp4-srv.rst index 1b8b8d7315..d8ce6e2453 100644 --- a/doc/sphinx/arm/dhcp4-srv.rst +++ b/doc/sphinx/arm/dhcp4-srv.rst @@ -4853,7 +4853,15 @@ following can be used: "valid-lifetime": 600, "subnet4": [ { "subnet": "10.0.0.0/24", - "reservation-mode": "global", + # It is deprecated by the "reservation-modes" map. + # "reservation-mode": "global", + # Reservation modes specifying server's mode of operation when it + # fetches host reservations. + "reservation-modes": { + "global": True, + "in-subnet": False, + "out-of-pool": False + }, "pools": [ { "pool": "10.0.0.10-10.0.0.100" } ] } ] } @@ -4956,7 +4964,15 @@ following example: "hw-address": "aa:bb:cc:dd:ee:fe", "client-classes": [ "reserved_class" ] }], - "reservation-mode": "global", + # It is deprecated by the "reservation-modes" map. + # "reservation-mode": "global", + # Reservation modes specifying server's mode of operation when it + # fetches host reservations. + "reservation-modes": { + "global": True, + "in-subnet": False, + "out-of-pool": False + }, "shared-networks": [{ "subnet4": [ { diff --git a/doc/sphinx/arm/dhcp6-srv.rst b/doc/sphinx/arm/dhcp6-srv.rst index a8c0ca577c..269c9ca5e3 100644 --- a/doc/sphinx/arm/dhcp6-srv.rst +++ b/doc/sphinx/arm/dhcp6-srv.rst @@ -4294,7 +4294,15 @@ following can be used: "valid-lifetime": 600, "subnet4": [ { "subnet": "2001:db8:1::/64", - "reservation-mode": "global", + # It is deprecated by the "reservation-modes" map. + # "reservation-mode": "global", + # Reservation modes specifying server's mode of operation when it + # fetches host reservations. + "reservation-modes": { + "global": True, + "in-subnet": False, + "out-of-pool": False + }, "pools": [ { "pool": "2001:db8:1::-2001:db8:1::100" } ] } ] } @@ -4397,7 +4405,15 @@ following example: "hw-address": "aa:bb:cc:dd:ee:fe", "client-classes": [ "reserved_class" ] }], - "reservation-mode": "global", + # It is deprecated by the "reservation-modes" map. + # "reservation-mode": "global", + # Reservation modes specifying server's mode of operation when it + # fetches host reservations. + "reservation-modes": { + "global": True, + "in-subnet": False, + "out-of-pool": False + }, "shared-networks": [{ "subnet6": [ { diff --git a/doc/sphinx/arm/hooks.rst b/doc/sphinx/arm/hooks.rst index 2790c00b41..6f81132bc9 100644 --- a/doc/sphinx/arm/hooks.rst +++ b/doc/sphinx/arm/hooks.rst @@ -2655,7 +2655,15 @@ An example response could look as follows: "ip-address": "0.0.0.0" }, "renew-timer": 60, - "reservation-mode": "all", + # It is deprecated by the "reservation-modes" map. + # "reservation-mode": "all", + # Reservation modes specifying server's mode of operation when it + # fetches host reservations. + "reservation-modes": { + "global": False, + "in-subnet": True, + "out-of-pool": True + }, "subnet4": [ { "subnet": "192.0.2.0/24", diff --git a/src/bin/dhcp4/json_config_parser.cc b/src/bin/dhcp4/json_config_parser.cc index 7d005cea6d..f200dd8f8a 100644 --- a/src/bin/dhcp4/json_config_parser.cc +++ b/src/bin/dhcp4/json_config_parser.cc @@ -564,6 +564,15 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set, } } + ConstElementPtr reservation_mode = mutable_cfg->get("reservation-mode"); + if (reservation_mode) { + reservation_mode = mutable_cfg->get("reservation-modes"); + if (reservation_mode) { + isc_throw(DhcpConfigError, "invalid use of both 'reservation-mode'" + " and 'reservation-modes' parameters"); + } + } + ConstElementPtr config_control = mutable_cfg->get("config-control"); if (config_control) { parameter_name = "config-control"; @@ -628,6 +637,7 @@ configureDhcp4Server(Dhcpv4Srv& server, isc::data::ConstElementPtr config_set, (config_pair.first == "boot-file-name") || (config_pair.first == "server-tag") || (config_pair.first == "reservation-mode") || + (config_pair.first == "reservation-modes") || (config_pair.first == "calculate-tee-times") || (config_pair.first == "t1-percent") || (config_pair.first == "t2-percent") || diff --git a/src/bin/dhcp4/parser_context.cc b/src/bin/dhcp4/parser_context.cc index fcbb91fbb4..91b4ea17c6 100644 --- a/src/bin/dhcp4/parser_context.cc +++ b/src/bin/dhcp4/parser_context.cc @@ -184,6 +184,8 @@ Parser4Context::contextName() return ("subnet4"); case RESERVATION_MODE: return ("reservation-mode"); + case RESERVATION_MODES: + return ("reservation-modes"); case OPTION_DEF: return ("option-def"); case OPTION_DATA: diff --git a/src/bin/dhcp4/parser_context.h b/src/bin/dhcp4/parser_context.h index 0bf2e7b40a..b4c7ca951b 100644 --- a/src/bin/dhcp4/parser_context.h +++ b/src/bin/dhcp4/parser_context.h @@ -84,7 +84,7 @@ public: /// This will parse the input as hooks-library. PARSER_HOOKS_LIBRARY, - /// This will parse the input as dhcp-ddns. + /// This will parse the input as dhcp-ddns. (D2 client config) PARSER_DHCP_DDNS, /// This will parse the input as reservation-modes. @@ -195,7 +195,7 @@ public: /// Check if a required parameter is present in the map at the top /// of the stack and raise an error when it is not. /// - /// @param name name of the parameter to check + /// @param name name of the parameter expected to be present /// @param open_loc location of the opening curly bracket /// @param close_loc location of the closing curly bracket /// @throw Dhcp4ParseError diff --git a/src/bin/dhcp6/json_config_parser.cc b/src/bin/dhcp6/json_config_parser.cc index 227fb54c16..3fff12c4d9 100644 --- a/src/bin/dhcp6/json_config_parser.cc +++ b/src/bin/dhcp6/json_config_parser.cc @@ -688,6 +688,15 @@ configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set, } } + ConstElementPtr reservation_mode = mutable_cfg->get("reservation-mode"); + if (reservation_mode) { + reservation_mode = mutable_cfg->get("reservation-modes"); + if (reservation_mode) { + isc_throw(DhcpConfigError, "invalid use of both 'reservation-mode'" + " and 'reservation-modes' parameters"); + } + } + ConstElementPtr config_control = mutable_cfg->get("config-control"); if (config_control) { parameter_name = "config-control"; @@ -761,6 +770,7 @@ configureDhcp6Server(Dhcpv6Srv& server, isc::data::ConstElementPtr config_set, (config_pair.first == "dhcp4o6-port") || (config_pair.first == "server-tag") || (config_pair.first == "reservation-mode") || + (config_pair.first == "reservation-modes") || (config_pair.first == "calculate-tee-times") || (config_pair.first == "t1-percent") || (config_pair.first == "t2-percent") || diff --git a/src/bin/dhcp6/parser_context.cc b/src/bin/dhcp6/parser_context.cc index d5b204c86f..03727fa260 100644 --- a/src/bin/dhcp6/parser_context.cc +++ b/src/bin/dhcp6/parser_context.cc @@ -181,6 +181,8 @@ Parser6Context::contextName() return ("subnet6"); case RESERVATION_MODE: return ("reservation-mode"); + case RESERVATION_MODES: + return ("reservation-modes"); case OPTION_DEF: return ("option-def"); case OPTION_DATA: diff --git a/src/bin/keactrl/kea-dhcp4.conf.pre b/src/bin/keactrl/kea-dhcp4.conf.pre index 5f77a32662..8bc8816761 100644 --- a/src/bin/keactrl/kea-dhcp4.conf.pre +++ b/src/bin/keactrl/kea-dhcp4.conf.pre @@ -360,7 +360,7 @@ // specific options. // // When using reservations, it is useful to configure - // reservation-mode (subnet specific parameter) and + // reservation-modes (subnet specific parameter) and // host-reservation-identifiers (global parameter). { "client-id": "01:12:23:34:45:56:67", diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am index 0f7a8bb554..9dee7779d1 100644 --- a/src/lib/dhcpsrv/Makefile.am +++ b/src/lib/dhcpsrv/Makefile.am @@ -45,6 +45,8 @@ EXTRA_DIST += parsers/ifaces_config_parser.h EXTRA_DIST += parsers/multi_threading_config_parser.cc EXTRA_DIST += parsers/multi_threading_config_parser.h EXTRA_DIST += parsers/option_data_parser.h +EXTRA_DIST += parsers/reservation_modes_parser.cc +EXTRA_DIST += parsers/reservation_modes_parser.h EXTRA_DIST += parsers/sanity_checks_parser.cc EXTRA_DIST += parsers/sanity_checks_parser.h EXTRA_DIST += parsers/simple_parser4.cc @@ -178,6 +180,8 @@ libkea_dhcpsrv_la_SOURCES += parsers/option_data_parser.cc libkea_dhcpsrv_la_SOURCES += parsers/option_data_parser.h libkea_dhcpsrv_la_SOURCES += parsers/dhcp_queue_control_parser.cc libkea_dhcpsrv_la_SOURCES += parsers/dhcp_queue_control_parser.h +libkea_dhcpsrv_la_SOURCES += parsers/reservation_modes_parser.cc +libkea_dhcpsrv_la_SOURCES += parsers/reservation_modes_parser.h libkea_dhcpsrv_la_SOURCES += parsers/sanity_checks_parser.cc libkea_dhcpsrv_la_SOURCES += parsers/sanity_checks_parser.h libkea_dhcpsrv_la_SOURCES += parsers/shared_network_parser.cc @@ -408,6 +412,7 @@ libkea_dhcpsrv_parsers_include_HEADERS = \ parsers/multi_threading_config_parser.h \ parsers/option_data_parser.h \ parsers/dhcp_queue_control_parser.h \ + parsers/reservation_modes_parser.h \ parsers/sanity_checks_parser.h \ parsers/shared_network_parser.h \ parsers/shared_networks_list_parser.h \ diff --git a/src/lib/dhcpsrv/network.h b/src/lib/dhcpsrv/network.h index 89a6951fe3..2408ad2b73 100644 --- a/src/lib/dhcpsrv/network.h +++ b/src/lib/dhcpsrv/network.h @@ -155,7 +155,7 @@ public: /// @brief Specifies allowed host reservation mode. /// - typedef enum { + typedef enum : uint8_t { /// None - host reservation is disabled. No reservation types /// are allowed. @@ -181,7 +181,10 @@ public: /// AllocEngine code has to check whether there are reservations, even /// when dealing with reservations from within the dynamic pools. HR_ALL = HR_IN_SUBNET | HR_OUT_OF_POOL - } HRMode; + } HRModeFlag; + + /// @brief Bitset used to store @ref HRModeFlag flags. + typedef uint8_t HRMode; /// @brief Inheritance "mode" used when fetching an optional @c Network /// parameter. @@ -811,14 +814,39 @@ protected: template ReturnType getGlobalProperty(ReturnType property, const std::string& global_name) const { - if (!global_name.empty() && fetch_globals_fn_) { + std::string member_name; + std::string search_name = global_name; + auto found = global_name.find('.'); + if (found != std::string::npos) { + if (std::count(global_name.begin(), global_name.end(), '.') > 1) { + isc_throw(BadValue, "more than one level of indirection found in: " + << global_name); + } + member_name = global_name.substr(found + 1, global_name.length() - found - 1); + search_name = global_name.substr(0, found); + } + if (!search_name.empty() && fetch_globals_fn_) { data::ConstElementPtr globals = fetch_globals_fn_(); if (globals && (globals->getType() == data::Element::map)) { - data::ConstElementPtr global_param = globals->get(global_name); + data::ConstElementPtr global_param = globals->get(search_name); if (global_param) { - // If there is a global parameter, convert it to the - // optional value of the given type and return. - return (data::ElementValue()(global_param)); + if (!member_name.empty()) { + if (global_param->getType() != data::Element::map) { + isc_throw(BadValue, "the parameter: " << global_name + << " must be a map"); + } + auto member_element = global_param->get(member_name); + if (member_element) { + // If there is a global parameter with the specified + // member, convert the member to the optional value + // of the given type and return. + return (data::ElementValue()(member_element)); + } + } else { + // If there is a global parameter, convert it to the + // optional value of the given type and return. + return (data::ElementValue()(global_param)); + } } } } diff --git a/src/lib/dhcpsrv/parsers/base_network_parser.cc b/src/lib/dhcpsrv/parsers/base_network_parser.cc index abfc37bcac..316cd2bbe2 100644 --- a/src/lib/dhcpsrv/parsers/base_network_parser.cc +++ b/src/lib/dhcpsrv/parsers/base_network_parser.cc @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -201,6 +202,10 @@ void BaseNetworkParser::parseHostReservationMode(const data::ConstElementPtr& network_data, NetworkPtr& network) { if (network_data->contains("reservation-mode")) { + if (network_data->contains("reservation-modes")) { + isc_throw(DhcpConfigError, "invalid use of both 'reservation-mode'" + " and 'reservation-modes' parameters"); + } try { std::string hr_mode = getString(network_data, "reservation-mode"); network->setHostReservationMode(Network::hrModeFromString(hr_mode)); @@ -212,6 +217,27 @@ BaseNetworkParser::parseHostReservationMode(const data::ConstElementPtr& network } } +void +BaseNetworkParser::parseHostReservationModes(const data::ConstElementPtr& network_data, + NetworkPtr& network) { + if (network_data->contains("reservation-modes")) { + if (network_data->contains("reservation-mode")) { + isc_throw(DhcpConfigError, "invalid use of both 'reservation-mode'" + " and 'reservation-modes' parameters"); + } + try { + auto reservation_modes = network_data->get("reservation-modes"); + HostReservationModesParser parser; + Network::HRMode flags = parser.parse(reservation_modes); + network->setHostReservationMode(flags); + } catch (const BadValue& ex) { + isc_throw(DhcpConfigError, "invalid reservation-modes parameter: " + << ex.what() << " (" << getPosition("reservation-modes", + network_data) << ")"); + } + } +} + void BaseNetworkParser::parseDdnsParams(const data::ConstElementPtr& network_data, NetworkPtr& network) { diff --git a/src/lib/dhcpsrv/parsers/base_network_parser.h b/src/lib/dhcpsrv/parsers/base_network_parser.h index fd2cdba6a5..e4791e102e 100644 --- a/src/lib/dhcpsrv/parsers/base_network_parser.h +++ b/src/lib/dhcpsrv/parsers/base_network_parser.h @@ -80,6 +80,9 @@ protected: /// @brief Parses host reservation mode. /// + /// @note Configuring 'reservation-mode' is deprecated. The new map + /// 'reservation-modes' should be used. + /// /// @param network_data Data element holding shared network /// configuration to be parsed. /// @param [out] network Pointer to a network in which parsed data is @@ -87,6 +90,15 @@ protected: void parseHostReservationMode(const data::ConstElementPtr& network_data, NetworkPtr& network); + /// @brief Parses host reservation modes. + /// + /// @param network_data Data element holding shared network + /// configuration to be parsed. + /// @param [out] network Pointer to a network in which parsed data is + /// to be stored. + void parseHostReservationModes(const data::ConstElementPtr& network_data, + NetworkPtr& network); + /// @brief Parses parameters pertaining to DDNS behavior. /// /// The parsed parameters are: diff --git a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc index c2b741a4bd..6044e3059f 100644 --- a/src/lib/dhcpsrv/parsers/dhcp_parsers.cc +++ b/src/lib/dhcpsrv/parsers/dhcp_parsers.cc @@ -859,7 +859,11 @@ Subnet4ConfigParser::initSubnet(data::ConstElementPtr params, } } - // Let's set host reservation mode. + // reservation-modes + parseHostReservationModes(params, network); + + // Let's set host reservation mode. If not specified, the default value of + // all will be used. parseHostReservationMode(params, network); // Try setting up client class. @@ -1330,6 +1334,9 @@ Subnet6ConfigParser::initSubnet(data::ConstElementPtr params, subnet6->setIface(iface); } + // reservation-modes + parseHostReservationModes(params, network); + // Let's set host reservation mode. If not specified, the default value of // all will be used. parseHostReservationMode(params, network); diff --git a/src/lib/dhcpsrv/parsers/reservation_modes_parser.cc b/src/lib/dhcpsrv/parsers/reservation_modes_parser.cc new file mode 100644 index 0000000000..978412d7a6 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/reservation_modes_parser.cc @@ -0,0 +1,59 @@ +// Copyright (C) 2015-2020 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 +#include +#include +#include +#include + +#include +#include + +using namespace isc::data; +using namespace isc::util; + +namespace isc { +namespace dhcp { + +Network::HRMode +HostReservationModesParser::parse(const ConstElementPtr& control_elem) { + if (control_elem->getType() != Element::map) { + isc_throw(DhcpConfigError, "reservation-modes must be a map"); + } + + ConstElementPtr elem; + uint8_t flags = 0; + + elem = control_elem->get("global"); + if (elem) { + bool value = elem->boolValue(); + if (value) { + flags |= Network::HR_GLOBAL; + } + } + + elem = control_elem->get("in-subnet"); + if (elem) { + bool value = elem->boolValue(); + if (value) { + flags |= Network::HR_IN_SUBNET; + } + } + + elem = control_elem->get("out-of-pool"); + if (elem) { + bool value = elem->boolValue(); + if (value) { + flags |= Network::HR_OUT_OF_POOL; + } + } + + return (static_cast(flags)); +} + +} // end of namespace isc::dhcp +} // end of namespace isc diff --git a/src/lib/dhcpsrv/parsers/reservation_modes_parser.h b/src/lib/dhcpsrv/parsers/reservation_modes_parser.h new file mode 100644 index 0000000000..bd244eb1e4 --- /dev/null +++ b/src/lib/dhcpsrv/parsers/reservation_modes_parser.h @@ -0,0 +1,46 @@ +// 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 RESERVATION_MODES_PARSER_H +#define RESERVATION_MODES_PARSER_H + +#include +#include +#include +#include + +namespace isc { +namespace dhcp { + +/// @brief Parser for the configuration of DHCP packet queue controls +/// +/// This parser parses the "reservation-modes" parameter which holds the +/// the configurable parameters that tailor host reservation modes. +/// +/// This parser is used in both DHCPv4 and DHCPv6, and also inside subnet and +/// shared networks. +class HostReservationModesParser : public isc::data::SimpleParser { +public: + + /// @brief Constructor + /// + HostReservationModesParser(){}; + + /// @brief Parses content of the "reservation-modes". + /// + /// @param control_elem MapElement containing the host reservation modes + /// values to parse + /// + /// @return Host reservation modes flags. + /// + /// @throw DhcpConfigError if any of the values are invalid. + Network::HRMode parse(const isc::data::ConstElementPtr& control_elem); +}; + +} +} // end of namespace isc + +#endif // RESERVATION_MODES_PARSER_H diff --git a/src/lib/dhcpsrv/parsers/shared_network_parser.cc b/src/lib/dhcpsrv/parsers/shared_network_parser.cc index 3f63ac8958..0bc95ba69a 100644 --- a/src/lib/dhcpsrv/parsers/shared_network_parser.cc +++ b/src/lib/dhcpsrv/parsers/shared_network_parser.cc @@ -185,6 +185,9 @@ SharedNetwork4Parser::parse(const data::ConstElementPtr& shared_network_data) { } } + // reservation-modes + parseHostReservationModes(shared_network_data, network); + // reservation-mode parseHostReservationMode(shared_network_data, network); @@ -351,6 +354,9 @@ SharedNetwork6Parser::parse(const data::ConstElementPtr& shared_network_data) { } } + // reservation-modes + parseHostReservationModes(shared_network_data, network); + // reservation-mode parseHostReservationMode(shared_network_data, network); diff --git a/src/lib/dhcpsrv/tests/Makefile.am b/src/lib/dhcpsrv/tests/Makefile.am index 8aad886da0..8b045e408b 100644 --- a/src/lib/dhcpsrv/tests/Makefile.am +++ b/src/lib/dhcpsrv/tests/Makefile.am @@ -87,6 +87,7 @@ libdhcpsrv_unittests_SOURCES += csv_lease_file6_unittest.cc libdhcpsrv_unittests_SOURCES += d2_client_unittest.cc libdhcpsrv_unittests_SOURCES += d2_udp_unittest.cc libdhcpsrv_unittests_SOURCES += dhcp_queue_control_parser_unittest.cc +libdhcpsrv_unittests_SOURCES += reservation_modes_parser_unittest.cc libdhcpsrv_unittests_SOURCES += dhcp4o6_ipc_unittest.cc libdhcpsrv_unittests_SOURCES += duid_config_parser_unittest.cc libdhcpsrv_unittests_SOURCES += expiration_config_parser_unittest.cc diff --git a/src/lib/dhcpsrv/tests/dhcp_queue_control_parser_unittest.cc b/src/lib/dhcpsrv/tests/dhcp_queue_control_parser_unittest.cc index cf3f102bd6..49b2d3eb48 100644 --- a/src/lib/dhcpsrv/tests/dhcp_queue_control_parser_unittest.cc +++ b/src/lib/dhcpsrv/tests/dhcp_queue_control_parser_unittest.cc @@ -48,7 +48,7 @@ DHCPQueueControlParserTest::TearDown() { } // Verifies that DHCPQueueControlParser handles -// expected valid dhcp-queue-control contet +// expected valid dhcp-queue-control content TEST_F(DHCPQueueControlParserTest, validContent) { struct Scenario { std::string description_; @@ -153,9 +153,8 @@ TEST_F(DHCPQueueControlParserTest, invalidContent) { } }; - // Iterate over the valid scenarios and verify they succeed. + // Iterate over the invalid scenarios and verify they throw exception. ConstElementPtr config_elems; - ConstElementPtr queue_control; for (auto scenario : scenarios) { SCOPED_TRACE(scenario.description_); { diff --git a/src/lib/dhcpsrv/tests/reservation_modes_parser_unittest.cc b/src/lib/dhcpsrv/tests/reservation_modes_parser_unittest.cc new file mode 100644 index 0000000000..7800067aa1 --- /dev/null +++ b/src/lib/dhcpsrv/tests/reservation_modes_parser_unittest.cc @@ -0,0 +1,165 @@ +// Copyright (C) 2018-2020 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 + +#include +#include +#include +#include +#include + +using namespace isc::data; +using namespace isc::dhcp; +using namespace isc::test; +using namespace isc::util; + +namespace { + +/// @brief Test fixture class for @c HostReservationModesParser +class HostReservationModesParserTest : public ::testing::Test { +protected: + + /// @brief Setup for each test. + /// + /// Clears the configuration in the @c CfgMgr. + virtual void SetUp(); + + /// @brief Cleans up after each test. + /// + /// Clears the configuration in the @c CfgMgr. + virtual void TearDown(); + +}; + +void +HostReservationModesParserTest::SetUp() { + CfgMgr::instance().clear(); +} + +void +HostReservationModesParserTest::TearDown() { + CfgMgr::instance().clear(); +} + +// Verifies that HostReservationModesParser handles +// expected valid reservation-modes content +TEST_F(HostReservationModesParserTest, validContent) { + struct Scenario { + std::string description_; + std::string json_; + }; + + std::vector scenarios = { + { + "queue disabled", + "{ \n" + " \"enable-queue\": false \n" + "} \n" + }, + { + "queue disabled, arbitrary content allowed", + "{ \n" + " \"enable-queue\": false, \n" + " \"foo\": \"bogus\", \n" + " \"random-int\" : 1234 \n" + "} \n" + }, + { + "queue enabled, with queue-type", + "{ \n" + " \"enable-queue\": true, \n" + " \"queue-type\": \"some-type\" \n" + "} \n" + }, + { + "queue enabled with queue-type and arbitrary content", + "{ \n" + " \"enable-queue\": true, \n" + " \"queue-type\": \"some-type\", \n" + " \"foo\": \"bogus\", \n" + " \"random-int\" : 1234 \n" + "} \n" + } + }; + + // Iterate over the valid scenarios and verify they succeed. + ConstElementPtr config_elems; + Network::HRMode reservation_modes; + for (auto scenario : scenarios) { + SCOPED_TRACE(scenario.description_); + { + // Construct the config JSON + ASSERT_NO_THROW(config_elems = Element::fromJSON(scenario.json_)) + << "invalid JSON, test is broken"; + + // Parsing config into a reservation modes parser should succeed. + HostReservationModesParser parser; + try { + reservation_modes = parser.parse(config_elems); + } catch (const std::exception& ex) { + ADD_FAILURE() << "parser threw an exception: " << ex.what(); + } + + // Verify the resultant reservation-modes. + ASSERT_TRUE(reservation_modes); + } + } +} + +// Verifies that HostReservationModesParser correctly catches +// invalid reservation-modes content +TEST_F(HostReservationModesParserTest, invalidContent) { + struct Scenario { + std::string description_; + std::string json_; + }; + + std::vector scenarios = { + { + "enable-queue missing", + "{ \n" + " \"enable-type\": \"some-type\" \n" + "} \n" + }, + { + "enable-queue not boolean", + "{ \n" + " \"enable-queue\": \"always\" \n" + "} \n" + }, + { + "queue enabled, type missing", + "{ \n" + " \"enable-queue\": true \n" + "} \n" + }, + { + "queue enabled, type not a string", + "{ \n" + " \"enable-queue\": true, \n" + " \"queue-type\": 7777 \n" + "} \n" + } + }; + + // Iterate over the invalid scenarios and verify they throw exception. + ConstElementPtr config_elems; + for (auto scenario : scenarios) { + SCOPED_TRACE(scenario.description_); + { + // Construct the config JSON + ASSERT_NO_THROW(config_elems = Element::fromJSON(scenario.json_)) + << "invalid JSON, test is broken"; + + // Parsing config into a reservation modes parser should succeed. + HostReservationModesParser parser; + EXPECT_THROW(parser.parse(config_elems), DhcpConfigError); + } + } +} + +}; // anonymous namespace