Browse Source

[4301] Host configuration parser now supports circuit-id parameter.

Marcin Siodelski 9 years ago
parent
commit
fb4c4a30e0

+ 9 - 6
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -3454,7 +3454,7 @@ TEST_F(Dhcp4ParserTest, reservations) {
         "        ]"
         "      },"
         "      {"
-        "        \"hw-address\": \"06:05:04:03:02:01\","
+        "        \"circuit-id\": \"060504030201\","
         "        \"ip-address\": \"192.0.4.102\","
         "        \"hostname\": \"\""
         "      }"
@@ -3528,16 +3528,19 @@ TEST_F(Dhcp4ParserTest, reservations) {
     ASSERT_TRUE(opt_ttl);
     EXPECT_EQ(32, static_cast<int>(opt_ttl->getValue()));
 
-    // The HW address used for one of the reservations in the subnet 542
+    // The circuit-id used for one of the reservations in the subnet 542
     // consists of numbers from 6 to 1. So, let's just reverse the order
     // of the address from the previous test.
-    hwaddr->hwaddr_.assign(hwaddr_vec.rbegin(), hwaddr_vec.rend());
-    host = hosts_cfg->get4(542, hwaddr);
+    std::vector<uint8_t> circuit_id(hwaddr_vec.rbegin(), hwaddr_vec.rend());
+    host = hosts_cfg->get4(542, Host::IDENT_CIRCUIT_ID, &circuit_id[0],
+                           circuit_id.size());
     EXPECT_TRUE(host);
     EXPECT_EQ("192.0.4.102", host->getIPv4Reservation().toText());
     // This reservation must not belong to other subnets.
-    EXPECT_FALSE(hosts_cfg->get4(123, hwaddr));
-    EXPECT_FALSE(hosts_cfg->get4(234, hwaddr));
+    EXPECT_FALSE(hosts_cfg->get4(123, Host::IDENT_CIRCUIT_ID,
+                                 &circuit_id[0], circuit_id.size()));
+    EXPECT_FALSE(hosts_cfg->get4(234, Host::IDENT_CIRCUIT_ID,
+                                 &circuit_id[0], circuit_id.size()));
 
     // Repeat the test for the DUID based reservation in this subnet.
     duid.reset(new DUID(std::vector<uint8_t>(duid_vec.rbegin(),

+ 85 - 30
src/lib/dhcpsrv/parsers/host_reservation_parser.cc

@@ -12,6 +12,7 @@
 #include <boost/foreach.hpp>
 #include <boost/lexical_cast.hpp>
 #include <sys/socket.h>
+#include <sstream>
 #include <string>
 
 using namespace isc::asiolink;
@@ -23,36 +24,62 @@ namespace {
 ///
 /// This function returns the set of supported parameters for
 /// host reservation in DHCPv4.
-const std::set<std::string>& getSupportedParams4() {
+///
+/// @param identifiers_only Indicates if the function should only
+/// return supported host identifiers (if true) or all supported
+/// parameters (if false).
+const std::set<std::string>&
+getSupportedParams4(const bool identifiers_only = false) {
+    // Holds set of host identifiers.
+    static std::set<std::string> identifiers_set;
+    // Holds set of all supported parameters, including identifiers.
     static std::set<std::string> params_set;
+    // If this is first execution of this function, we need
+    // to initialize the set.
+    if (identifiers_set.empty()) {
+        identifiers_set.insert("duid");
+        identifiers_set.insert("hw-address");
+        identifiers_set.insert("circuit-id");
+    }
+    // Copy identifiers and add all other parameters.
     if (params_set.empty()) {
-        const char* params[] = {
-            "duid", "hw-address", "hostname", "ip-address",
-            "option-data", NULL
-        };
-        for (int i = 0; params[i] != NULL; ++i) {
-            params_set.insert(std::string(params[i]));
-        }
+        params_set = identifiers_set;
+        params_set.insert("hostname");
+        params_set.insert("ip-address");
+        params_set.insert("option-data");
     }
-    return (params_set);
+    return (identifiers_only ? identifiers_set : params_set);
 }
 
-/// @brief Returns set of the supported parameters for DHCPv4.
+/// @brief Returns set of the supported parameters for DHCPv6.
 ///
 /// This function returns the set of supported parameters for
 /// host reservation in DHCPv6.
-const std::set<std::string>& getSupportedParams6() {
+///
+/// @param identifiers_only Indicates if the function should only
+/// return supported host identifiers (if true) or all supported
+/// parameters (if false).
+const std::set<std::string>&
+getSupportedParams6(const bool identifiers_only = false) {
+    // Holds set of host identifiers.
+    static std::set<std::string> identifiers_set;
+    // Holds set of all supported parameters, including identifiers.
     static std::set<std::string> params_set;
+    // If this is first execution of this function, we need
+    // to initialize the set.
+    if (identifiers_set.empty()) {
+        identifiers_set.insert("duid");
+        identifiers_set.insert("hw-address");
+    }
+    // Copy identifiers and add all other parameters.
     if (params_set.empty()) {
-        const char* params[] = {
-            "duid", "hw-address", "hostname", "ip-addresses", "prefixes",
-            "option-data", NULL
-        };
-        for (int i = 0; params[i] != NULL; ++i) {
-            params_set.insert(std::string(params[i]));
-        }
+        params_set = identifiers_set;
+        params_set.insert("hostname");
+        params_set.insert("ip-addresses");
+        params_set.insert("prefixes");
+        params_set.insert("option-data");
     }
-    return (params_set);
+    return (identifiers_only ? identifiers_set : params_set);
 }
 
 }
@@ -80,10 +107,11 @@ HostReservationParser::build(isc::data::ConstElementPtr reservation_data) {
                           " parameter '" << element.first << "'");
             }
 
-            if (element.first == "hw-address" || element.first == "duid") {
-                if (!identifier_name.empty()) {
-                    isc_throw(DhcpConfigError, "the 'hw-address' and 'duid'"
-                              " parameters are mutually exclusive");
+            if (isIdentifierParameter(element.first)) {
+                if (!identifier.empty()) {
+                    isc_throw(DhcpConfigError, "the '" << element.first
+                              << "' and '" << identifier_name
+                              << "' are mutually exclusive");
                 }
                 identifier = element.second->stringValue();
                 identifier_name = element.first;
@@ -100,10 +128,22 @@ HostReservationParser::build(isc::data::ConstElementPtr reservation_data) {
     }
 
     try {
-        // hw-address or duid is a must.
+        // Host identifier is a must.
         if (identifier_name.empty()) {
-            isc_throw(DhcpConfigError, "'hw-address' or 'duid' is a required"
-                      " parameter for host reservation");
+            // If there is no identifier specified, we have to display an
+            // error message and include the information what identifiers
+            // are supported.
+            std::ostringstream s;
+            BOOST_FOREACH(std::string param_name, getSupportedParameters(true)) {
+                if (s.tellp() != std::streampos(0)) {
+                    s << ", ";
+                }
+                s << param_name;
+            }
+            isc_throw(DhcpConfigError, "one of the supported identifiers must"
+                      " be specified for host reservation: "
+                      << s.str());
+
         }
 
         // Create a host object from the basic parameters we already parsed.
@@ -129,6 +169,11 @@ HostReservationParser::addHost(isc::data::ConstElementPtr reservation_data) {
     }
 }
 
+bool
+HostReservationParser::isSupportedParameter(const std::string& param_name) const {
+    return (getSupportedParameters(false).count(param_name) > 0);
+}
+
 HostReservationParser4::HostReservationParser4(const SubnetID& subnet_id)
     : HostReservationParser(subnet_id) {
 }
@@ -168,8 +213,13 @@ HostReservationParser4::build(isc::data::ConstElementPtr reservation_data) {
 }
 
 bool
-HostReservationParser4::isSupportedParameter(const std::string& param_name) const {
-    return (getSupportedParams4().count(param_name) > 0);
+HostReservationParser4::isIdentifierParameter(const std::string& param_name) const {
+    return (getSupportedParams4(true).count(param_name) > 0);
+}
+
+const std::set<std::string>&
+HostReservationParser4::getSupportedParameters(const bool identifiers_only) const {
+    return (getSupportedParams4(identifiers_only));
 }
 
 HostReservationParser6::HostReservationParser6(const SubnetID& subnet_id)
@@ -263,8 +313,13 @@ HostReservationParser6::build(isc::data::ConstElementPtr reservation_data) {
 }
 
 bool
-HostReservationParser6::isSupportedParameter(const std::string& param_name) const {
-    return (getSupportedParams6().count(param_name) > 0);
+HostReservationParser6::isIdentifierParameter(const std::string& param_name) const {
+    return (getSupportedParams6(true).count(param_name) > 0);
+}
+
+const std::set<std::string>&
+HostReservationParser6::getSupportedParameters(const bool identifiers_only) const {
+    return (getSupportedParams6(identifiers_only));
 }
 
 } // end of namespace isc::dhcp

+ 50 - 8
src/lib/dhcpsrv/parsers/host_reservation_parser.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -48,12 +48,30 @@ protected:
     /// @throw DhcpConfigError When operation to add a configured host fails.
     void addHost(isc::data::ConstElementPtr reservation_data);
 
+    /// @brief Checks if the specified parameter is a host identifier.
+    ///
+    /// @param param_name Parameter name.
+    ///
+    /// @return true if the parameter specifies host identifier, false
+    /// otherwise.
+    virtual bool isIdentifierParameter(const std::string& param_name) const = 0;
+
     /// @brief Checks if the specified parameter is supported by the parser.
     ///
     /// @param param_name Parameter name.
     ///
     /// @return true if the parameter is supported, false otherwise.
-    virtual bool isSupportedParameter(const std::string& param_name) const = 0;
+    virtual bool isSupportedParameter(const std::string& param_name) const;
+
+    /// @brief Returns set of the supported parameters.
+    ///
+    /// @param identifiers_only Indicates if the function should only
+    /// return supported host identifiers (if true) or all supported
+    /// parameters (if false).
+    ///
+    /// @return Set of supported parameter names.
+    virtual const std::set<std::string>&
+    getSupportedParameters(const bool identifiers_only) const = 0;
 
     /// @brief Identifier of the subnet that the host is connected to.
     SubnetID subnet_id_;
@@ -84,12 +102,24 @@ public:
 
 protected:
 
-    /// @brief Checks if the specified parameter is supported by the parser.
+    /// @brief Checks if the specified parameter is a host identifier.
     ///
     /// @param param_name Parameter name.
     ///
-    /// @return true if the parameter is supported, false otherwise.
-    virtual bool isSupportedParameter(const std::string& param_name) const;
+    /// @return true if the parameter specifies host identifier, false
+    /// otherwise.
+    virtual bool isIdentifierParameter(const std::string& param_name) const;
+
+    /// @brief Returns set of the supported parameters for DHCPv4.
+    ///
+    /// @param identifiers_only Indicates if the function should only
+    /// return supported host identifiers (if true) or all supported
+    /// parameters (if false).
+    ///
+    /// @return Set of supported parameter names.
+    virtual const std::set<std::string>&
+    getSupportedParameters(const bool identifiers_only) const;
+
 };
 
 /// @brief Parser for a single host reservation for DHCPv6.
@@ -112,12 +142,24 @@ public:
 
 protected:
 
-    /// @brief Checks if the specified parameter is supported by the parser.
+    /// @brief Checks if the specified parameter is a host identifier.
     ///
     /// @param param_name Parameter name.
     ///
-    /// @return true if the parameter is supported, false otherwise.
-    virtual bool isSupportedParameter(const std::string& param_name) const;
+    /// @return true if the parameter specifies host identifier, false
+    /// otherwise.
+    virtual bool isIdentifierParameter(const std::string& param_name) const;
+
+    /// @brief Returns set of the supported parameters for DHCPv6.
+    ///
+    /// @param identifiers_only Indicates if the function should only
+    /// return supported host identifiers (if true) or all supported
+    /// parameters (if false).
+    ///
+    /// @return Set of supported parameter names.
+    virtual const std::set<std::string>&
+    getSupportedParameters(const bool identifiers_only) const;
+
 };
 
 

+ 72 - 0
src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc

@@ -21,6 +21,7 @@
 #include <gtest/gtest.h>
 #include <iterator>
 #include <string>
+#include <vector>
 
 using namespace isc::asiolink;
 using namespace isc::data;
@@ -126,6 +127,40 @@ protected:
         EXPECT_TRUE(hosts[0]->getCfgOption6()->empty());
     }
 
+    /// @brief This test verfies that the parser can parse a DHCPv4
+    /// reservation configuration including a specific identifier.
+    ///
+    /// @param identifier_name Identifier name.
+    /// @param identifier_type Identifier type.
+    void testIdentifier4(const std::string& identifier_name,
+                         const std::string& identifier_value,
+                         const Host::IdentifierType& expected_identifier_type,
+                         const std::vector<uint8_t>& expected_identifier) const {
+        std::ostringstream config;
+        config << "{ \"" << identifier_name << "\": \"" << identifier_value
+               << "\","
+               << "\"ip-address\": \"192.0.2.112\","
+               << "\"hostname\": \"\" }";
+
+        ElementPtr config_element = Element::fromJSON(config.str());
+
+        HostReservationParser4 parser(SubnetID(10));
+        ASSERT_NO_THROW(parser.build(config_element));
+
+        CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+        HostCollection hosts;
+        ASSERT_NO_THROW(hosts = cfg_hosts->getAll(expected_identifier_type,
+                                                  &expected_identifier[0],
+                                                  expected_identifier.size()));
+
+        ASSERT_EQ(1, hosts.size());
+
+        EXPECT_EQ(10, hosts[0]->getIPv4SubnetID());
+        EXPECT_EQ(0, hosts[0]->getIPv6SubnetID());
+        EXPECT_EQ("192.0.2.112", hosts[0]->getIPv4Reservation().toText());
+        EXPECT_TRUE(hosts[0]->getHostname().empty());
+    }
+
     /// @brief This test verfies that the parser returns an error when
     /// configuration is invalid.
     ///
@@ -144,6 +179,8 @@ protected:
     /// @brief DUID object used by tests.
     DuidPtr duid_;
 
+    /// @brief Vector holding circuit id used by tests.
+    std::vector<uint8_t> circuit_id_;
 };
 
 void
@@ -158,6 +195,9 @@ HostReservationParserTest::SetUp() {
     const uint8_t duid_data[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
                                   0x08, 0x09, 0x0A };
     duid_ = DuidPtr(new DUID(duid_data, sizeof(duid_data)));
+
+    const std::string circuit_id_str = "howdy";
+    circuit_id_.assign(circuit_id_str.begin(), circuit_id_str.end());
 }
 
 void
@@ -213,6 +253,22 @@ TEST_F(HostReservationParserTest, dhcp4DUID) {
     EXPECT_TRUE(hosts[0]->getHostname().empty());
 }
 
+// This test verifies that the parser can parse a reservation entry for
+// which circuit-id is an identifier. The circuit-id is specified as
+// a string in quotes.
+TEST_F(HostReservationParserTest, dhcp4CircuitIdStringInQuotes) {
+    testIdentifier4("circuit-id", "'howdy'", Host::IDENT_CIRCUIT_ID,
+                    circuit_id_);
+}
+
+// This test verifies that the parser can parse a reservation entry for
+// which circuit-id is an identifier. The circuit-id is specified in
+// hexadecimal format.
+TEST_F(HostReservationParserTest, dhcp4CircuitIdHex) {
+    testIdentifier4("circuit-id", "686F776479", Host::IDENT_CIRCUIT_ID,
+                    circuit_id_);
+}
+
 // This test verifies that the parser can parse the reservation entry
 // when IPv4 address is specified, but hostname is not.
 TEST_F(HostReservationParserTest, dhcp4NoHostname) {
@@ -447,6 +503,22 @@ TEST_F(HostReservationParserTest, dhcp6DUID) {
     ASSERT_EQ(0, std::distance(prefixes.first, prefixes.second));
 }
 
+// This test verifies that host reservation parser for DHCPv6 rejects
+// "circuit-id" as a host identifier.
+TEST_F(HostReservationParserTest, dhcp6CircuitId) {
+    // Use DHCPv4 specific identifier 'circuit-id' with DHCPv6 parser.
+    std::string config = "{ \"circuit-id\": \"'howdy'\","
+        "\"ip-addresses\": [ \"2001:db8:1::100\", \"2001:db8:1::200\" ],"
+        "\"prefixes\": [ ],"
+        "\"hostname\": \"foo.example.com\" }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    // The parser should throw exception.
+    HostReservationParser6 parser(SubnetID(12));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
 // This test verfies that the parser can parse the IPv6 reservation entry
 // which lacks hostname parameter.
 TEST_F(HostReservationParserTest, dhcp6NoHostname) {