]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1405] added parser class
authorRazvan Becheriu <razvan@isc.org>
Fri, 9 Oct 2020 10:12:28 +0000 (13:12 +0300)
committerRazvan Becheriu <razvan@isc.org>
Wed, 18 Nov 2020 13:55:22 +0000 (15:55 +0200)
20 files changed:
doc/sphinx/arm/dhcp4-srv.rst
doc/sphinx/arm/dhcp6-srv.rst
doc/sphinx/arm/hooks.rst
src/bin/dhcp4/json_config_parser.cc
src/bin/dhcp4/parser_context.cc
src/bin/dhcp4/parser_context.h
src/bin/dhcp6/json_config_parser.cc
src/bin/dhcp6/parser_context.cc
src/bin/keactrl/kea-dhcp4.conf.pre
src/lib/dhcpsrv/Makefile.am
src/lib/dhcpsrv/network.h
src/lib/dhcpsrv/parsers/base_network_parser.cc
src/lib/dhcpsrv/parsers/base_network_parser.h
src/lib/dhcpsrv/parsers/dhcp_parsers.cc
src/lib/dhcpsrv/parsers/reservation_modes_parser.cc [new file with mode: 0644]
src/lib/dhcpsrv/parsers/reservation_modes_parser.h [new file with mode: 0644]
src/lib/dhcpsrv/parsers/shared_network_parser.cc
src/lib/dhcpsrv/tests/Makefile.am
src/lib/dhcpsrv/tests/dhcp_queue_control_parser_unittest.cc
src/lib/dhcpsrv/tests/reservation_modes_parser_unittest.cc [new file with mode: 0644]

index 1b8b8d7315bcbb7a6d6fb32cfea9eb64a2cb3399..d8ce6e24536bd5899bef5f5e6519605f89a9987d 100644 (file)
@@ -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": [
                 {
index a8c0ca577c41d6113eca406281688634832dad42..269c9ca5e3a7b64c2defcc422cb230c47d3e5514 100644 (file)
@@ -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": [
                 {
index 2790c00b4185078d5d126c62472c287163bf2f25..6f81132bc9e80b8949a67fdd0c38be00b9bcaa40 100644 (file)
@@ -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",
index 7d005cea6dc09318e5bf7071ecc824c1899c1218..f200dd8f8a0161d5695d70aecd773ce4844733cc 100644 (file)
@@ -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") ||
index fcbb91fbb449dd69f0be96f13572bbbb04fdf7c6..91b4ea17c684ec53ee050326e01ec6729f828602 100644 (file)
@@ -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:
index 0bf2e7b40a99aebc63b9635dfa07d603551f6335..b4c7ca951b8c6eafb8a3be3c3d8f65f3222cff26 100644 (file)
@@ -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
index 227fb54c16cecb68ddc378fd4e124eb04d7fece1..3fff12c4d9612c4ad405b36ca14bbd69b499712c 100644 (file)
@@ -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") ||
index d5b204c86f580d8848f5f170926ae5881eae2d08..03727fa26018a03ac725d5994414e2100bb356f5 100644 (file)
@@ -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:
index 5f77a32662ad76ec323ea22d4bbd637c65f67ea3..8bc88167617a27f60a85af00718e41be961b12ca 100644 (file)
                 // 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",
index 0f7a8bb55438265c274a394a3a93ebeef0a56db8..9dee7779d1f991f9c440e6ccc18d9d0e1d180ab6 100644 (file)
@@ -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 \
index 89a6951fe364bfe0141930ecf99c6af8e48d39e9..2408ad2b730cf207013a62ca8dd0ae7c9ea1cf02 100644 (file)
@@ -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<typename ReturnType>
     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<typename ReturnType::ValueType>()(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<typename ReturnType::ValueType>()(member_element));
+                        }
+                    } else {
+                        // If there is a global parameter, convert it to the
+                        // optional value of the given type and return.
+                        return (data::ElementValue<typename ReturnType::ValueType>()(global_param));
+                    }
                 }
             }
         }
index abfc37bcace3072afd811309f9acd8d35a5f9225..316cd2bbe2d2d267d46a277bc2f656279036a287 100644 (file)
@@ -7,6 +7,7 @@
 #include <config.h>
 #include <dhcpsrv/triplet.h>
 #include <dhcpsrv/parsers/base_network_parser.h>
+#include <dhcpsrv/parsers/reservation_modes_parser.h>
 #include <util/optional.h>
 #include <util/strutil.h>
 
@@ -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) {
index fd2cdba6a5db5d8e17e14dfb4283a97c6a5905ad..e4791e102e93632adb1b5d8b055ba757bae6596c 100644 (file)
@@ -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:
index c2b741a4bde860bc00b56873ad5764cb3ed77460..6044e3059f693afb03b4d83b0ad27a7068cd48bb 100644 (file)
@@ -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 (file)
index 0000000..978412d
--- /dev/null
@@ -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 <config.h>
+#include <cc/data.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/dhcpsrv_log.h>
+#include <dhcpsrv/parsers/reservation_modes_parser.h>
+
+#include <string>
+#include <sys/types.h>
+
+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<Network::HRMode>(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 (file)
index 0000000..bd244eb
--- /dev/null
@@ -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 <cc/data.h>
+#include <cc/simple_parser.h>
+#include <dhcpsrv/network.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
+
+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
index 3f63ac8958a4e10406e2a147dbdc88d555a15168..0bc95ba69a343e88c3a3ba099942b709ce86867a 100644 (file)
@@ -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);
 
index 8aad886da0fdd6bc4d1e39858781e80c5ad1c0b9..8b045e408b684ac1a2622fe258a2571e9762f229 100644 (file)
@@ -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
index cf3f102bd63826fc57f9405eff7a2eeb4edc1ceb..49b2d3eb4883ec7c4ad0e8732fac04835333be70 100644 (file)
@@ -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 (file)
index 0000000..7800067
--- /dev/null
@@ -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 <config.h>
+
+#include <cc/data.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/parsers/reservation_modes_parser.h>
+#include <testutils/test_to_element.h>
+#include <gtest/gtest.h>
+
+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<Scenario> 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<Scenario> 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