Browse Source

[3628] Host reservation parser is now connected to the subnet parsers.

Marcin Siodelski 10 years ago
parent
commit
aacdfadac9

+ 10 - 0
src/bin/dhcp4/json_config_parser.cc

@@ -22,6 +22,8 @@
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/parsers/dbaccess_parser.h>
 #include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <dhcpsrv/parsers/host_reservation_parser.h>
+#include <dhcpsrv/parsers/host_reservations_list_parser.h>
 #include <util/encode/hex.h>
 #include <util/strutil.h>
 
@@ -144,6 +146,14 @@ public:
                           << subnet->getPosition() << ")");
             }
         }
+
+        // Parse Host Reservations for this subnet if any.
+        ConstElementPtr reservations = subnet->get("reservations");
+        if (reservations) {
+            ParserPtr parser(new HostReservationsListParser<
+                             HostReservationParser4>(subnet_->getID()));
+            parser->build(reservations);
+        }
     }
 
     /// @brief Commits subnet configuration.

+ 147 - 0
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -28,6 +28,7 @@
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_hosts.h>
 #include <dhcpsrv/cfg_subnets4.h>
 #include <dhcpsrv/testutils/config_result_check.h>
 #include <hooks/hooks_manager.h>
@@ -3237,4 +3238,150 @@ TEST_F(Dhcp4ParserTest, classifySubnets) {
     EXPECT_TRUE (subnets->at(3)->clientSupported(classes));
 }
 
+// This test verifies that the host reservations can be specified for
+// respective IPv4 subnets.
+TEST_F(Dhcp4ParserTest, reservations) {
+    ConstElementPtr x;
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.2.1 - 192.0.2.100\" } ],"
+        "    \"subnet\": \"192.0.2.0/24\", "
+        "    \"id\": 123,"
+        "    \"reservations\": ["
+        "    ]"
+        " },"
+        " {"
+        "    \"reservations\": ["
+        "      {"
+        "        \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "        \"ip-address\": \"192.0.3.112\","
+        "        \"hostname\": \"\""
+        "      },"
+        "      {"
+        "        \"hw-address\": \"01:02:03:04:05:06\","
+        "        \"ip-address\": \"192.0.3.120\","
+        "        \"hostname\": \"\""
+        "      }"
+        "    ],"
+        "    \"pools\": [ { \"pool\": \"192.0.3.101 - 192.0.3.150\" } ],"
+        "    \"subnet\": \"192.0.3.0/24\", "
+        "    \"id\": 234"
+        " },"
+        " {"
+        "    \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
+        "    \"subnet\": \"192.0.4.0/24\","
+        "    \"id\": 542,"
+        "    \"reservations\": ["
+        "      {"
+        "        \"duid\": \"0A:09:08:07:06:05:04:03:02:01\","
+        "        \"ip-address\": \"192.0.4.101\","
+        "        \"hostname\": \"\""
+        "      },"
+        "      {"
+        "        \"hw-address\": \"06:05:04:03:02:01\","
+        "        \"ip-address\": \"192.0.4.102\","
+        "        \"hostname\": \"\""
+        "      }"
+        "    ]"
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+    checkResult(x, 0);
+
+    // Make sure all subnets have been successfully configured. There is no
+    // need to sanity check the subnet properties because it should have
+    // been already tested by other tests.
+    const Subnet4Collection* subnets =
+        CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->getAll();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(3, subnets->size());
+
+    // Hosts configuration must be available.
+    CfgHostsPtr hosts_cfg = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+    ASSERT_TRUE(hosts_cfg);
+
+    // Let's create an object holding hardware address of the host having
+    // a reservation in the subnet having id of 234. For simlicity the
+    // address is a collection of numbers from 1 to 6.
+    std::vector<uint8_t> hwaddr_vec;
+    for (int i = 1; i < 7; ++i) {
+        hwaddr_vec.push_back(static_cast<uint8_t>(i));
+    }
+    HWAddrPtr hwaddr(new HWAddr(hwaddr_vec, HTYPE_ETHER));
+    // Retrieve the reservation and sanity check the address reserved.
+    ConstHostPtr host = hosts_cfg->get4(234, hwaddr);
+    ASSERT_TRUE(host);
+    EXPECT_EQ("192.0.3.120", host->getIPv4Reservation().toText());
+    // This reservation should be solely assigned to the subnet 234,
+    // and not to other two.
+    EXPECT_FALSE(hosts_cfg->get4(123, hwaddr));
+    EXPECT_FALSE(hosts_cfg->get4(542, hwaddr));
+
+    // Do the same test for the DUID based reservation.
+    std::vector<uint8_t> duid_vec;
+    for (int i = 1; i < 0xb; ++i) {
+        duid_vec.push_back(static_cast<uint8_t>(i));
+    }
+    DuidPtr duid(new DUID(duid_vec));
+    host = hosts_cfg->get4(234, HWAddrPtr(), duid);
+    ASSERT_TRUE(host);
+    EXPECT_EQ("192.0.3.112", host->getIPv4Reservation().toText());
+    EXPECT_FALSE(hosts_cfg->get4(123, HWAddrPtr(), duid));
+    EXPECT_FALSE(hosts_cfg->get4(542, HWAddrPtr(), duid));
+
+    // The HW address 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);
+    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));
+
+    // Repeat the test for the DUID based reservation in this subnet.
+    duid.reset(new DUID(std::vector<uint8_t>(duid_vec.rbegin(),
+                                             duid_vec.rend())));
+    host = hosts_cfg->get4(542, HWAddrPtr(), duid);
+    ASSERT_TRUE(host);
+    EXPECT_EQ("192.0.4.101", host->getIPv4Reservation().toText());
+    EXPECT_FALSE(hosts_cfg->get4(123, HWAddrPtr(), duid));
+    EXPECT_FALSE(hosts_cfg->get4(234, HWAddrPtr(), duid));
+
+}
+
+// This test verfies that the bogus host reservation would trigger a
+// server configuration error. In this case the "hw-address" parameter in the
+// reservation is misspelled.
+TEST_F(Dhcp4ParserTest, reservationBogus) {
+    ConstElementPtr x;
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [ { "
+        "    \"pools\": [ { \"pool\": \"192.0.4.101 - 192.0.4.150\" } ],"
+        "    \"subnet\": \"192.0.4.0/24\","
+        "    \"id\": 542,"
+        "    \"reservations\": ["
+        "      {"
+        "        \"hw-addre\": \"06:05:04:03:02:01\","
+        "        \"ip-address\": \"192.0.4.102\","
+        "        \"hostname\": \"\""
+        "      }"
+        "    ]"
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+    checkResult(x, 1);
+}
+
 }

+ 9 - 0
src/bin/dhcp6/json_config_parser.cc

@@ -27,6 +27,8 @@
 #include <dhcpsrv/parsers/dbaccess_parser.h>
 #include <dhcpsrv/parsers/dhcp_config_parser.h>
 #include <dhcpsrv/parsers/dhcp_parsers.h>
+#include <dhcpsrv/parsers/host_reservation_parser.h>
+#include <dhcpsrv/parsers/host_reservations_list_parser.h>
 #include <log/logger_support.h>
 #include <util/encode/hex.h>
 #include <util/strutil.h>
@@ -350,6 +352,13 @@ public:
                           << subnet->getPosition() << ")");
             }
 
+            // Parse Host Reservations for this subnet if any.
+            ConstElementPtr reservations = subnet->get("reservations");
+            if (reservations) {
+                ParserPtr parser(new HostReservationsListParser<
+                                 HostReservationParser6>(subnet_->getID()));
+                parser->build(reservations);
+            }
         }
     }
 

+ 181 - 0
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -25,6 +25,7 @@
 #include <dhcp6/dhcp6_srv.h>
 #include <dhcpsrv/addr_utilities.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_hosts.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/subnet_selector.h>
 #include <dhcpsrv/testutils/config_result_check.h>
@@ -3384,4 +3385,184 @@ TEST_F(Dhcp6ParserTest, invalidD2ClientConfig) {
     ASSERT_FALSE(CfgMgr::instance().ddnsEnabled());
 }
 
+/// @brief Checks if the reservation is in the range of reservations.
+///
+/// @param resrv Reservation to be searched for.
+/// @param range Range of reservations returned by the @c Host object
+/// in which the reservation will be searched.
+bool reservationExists(const IPv6Resrv& resrv, const IPv6ResrvRange& range) {
+    for (IPv6ResrvIterator it = range.first; it != range.second;
+         ++it) {
+        if (resrv == it->second) {
+            return (true);
+        }
+    }
+    return (false);
+}
+
+// This test verifies that the host reservations can be specified for
+// respective IPv6 subnets.
+TEST_F(Dhcp6ParserTest, reservations) {
+    ConstElementPtr x;
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pools\": [ { \"pool\": \"2001:db8:1::/80\" } ],"
+        "    \"subnet\": \"2001:db8:1::/64\", "
+        "    \"id\": 123,"
+        "    \"reservations\": ["
+        "    ]"
+        " },"
+        " {"
+        "    \"reservations\": ["
+        "      {"
+        "        \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "        \"ip-addresses\": [ \"2001:db8:2::1234\" ],"
+        "        \"hostname\": \"\""
+        "      },"
+        "      {"
+        "        \"hw-address\": \"01:02:03:04:05:06\","
+        "        \"ip-addresses\": [ \"2001:db8:2::abcd\" ],"
+        "        \"hostname\": \"\""
+        "      }"
+        "    ],"
+        "    \"pools\": [ ],"
+        "    \"subnet\": \"2001:db8:2::/64\", "
+        "    \"id\": 234"
+        " },"
+        " {"
+        "    \"pools\": [ ],"
+        "    \"subnet\": \"2001:db8:3::/64\", "
+        "    \"id\": 542,"
+        "    \"reservations\": ["
+        "      {"
+        "        \"duid\": \"0A:09:08:07:06:05:04:03:02:01\","
+        "        \"prefixes\": [ \"2001:db8:3:2::/96\" ],"
+        "        \"hostname\": \"\""
+        "      },"
+        "      {"
+        "        \"hw-address\": \"06:05:04:03:02:01\","
+        "        \"prefixes\": [ \"2001:db8:3:1::/96\" ],"
+        "        \"hostname\": \"\""
+        "      }"
+        "    ]"
+        " } ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+    checkResult(x, 0);
+
+    // Make sure all subnets have been successfully configured. There is no
+    // need to sanity check the subnet properties because it should have
+    // been already tested by other tests.
+    const Subnet6Collection* subnets =
+        CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->getAll();
+    ASSERT_TRUE(subnets);
+    ASSERT_EQ(3, subnets->size());
+
+    // Hosts configuration must be available.
+    CfgHostsPtr hosts_cfg = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+    ASSERT_TRUE(hosts_cfg);
+
+    // Let's create an object holding hardware address of the host having
+    // a reservation in the subnet having id of 234. For simlicity the
+    // address is a collection of numbers from 1 to 6.
+    std::vector<uint8_t> hwaddr_vec;
+    for (int i = 1; i < 7; ++i) {
+        hwaddr_vec.push_back(static_cast<uint8_t>(i));
+    }
+    HWAddrPtr hwaddr(new HWAddr(hwaddr_vec, HTYPE_ETHER));
+    // Retrieve the reservation and sanity check the address reserved.
+    ConstHostPtr host = hosts_cfg->get6(234, DuidPtr(), hwaddr);
+    ASSERT_TRUE(host);
+    IPv6ResrvRange resrv = host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+    ASSERT_EQ(1, std::distance(resrv.first, resrv.second));
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                            IOAddress("2001:db8:2::abcd")),
+                                  resrv));
+    // This reservation should be solely assigned to the subnet 234,
+    // and not to other two.
+    EXPECT_FALSE(hosts_cfg->get6(123, DuidPtr(), hwaddr));
+    EXPECT_FALSE(hosts_cfg->get6(542, DuidPtr(), hwaddr));
+
+    // Do the same test for the DUID based reservation.
+    std::vector<uint8_t> duid_vec;
+    for (int i = 1; i < 0xb; ++i) {
+        duid_vec.push_back(static_cast<uint8_t>(i));
+    }
+    DuidPtr duid(new DUID(duid_vec));
+    host = hosts_cfg->get6(234, duid);
+    ASSERT_TRUE(host);
+    resrv = host->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+    ASSERT_EQ(1, std::distance(resrv.first, resrv.second));
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                            IOAddress("2001:db8:2::1234")),
+                                  resrv));
+    EXPECT_FALSE(hosts_cfg->get6(123, duid));
+    EXPECT_FALSE(hosts_cfg->get6(542, duid));
+
+    // The HW address 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->get6(542, DuidPtr(), hwaddr);
+    EXPECT_TRUE(host);
+    resrv = host->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+    ASSERT_EQ(1, std::distance(resrv.first, resrv.second));
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_PD,
+                                            IOAddress("2001:db8:3:1::"),
+                                            96), resrv));
+
+    // This reservation must not belong to other subnets.
+    EXPECT_FALSE(hosts_cfg->get6(123, DuidPtr(), hwaddr));
+    EXPECT_FALSE(hosts_cfg->get6(234, DuidPtr(), hwaddr));
+
+    // Repeat the test for the DUID based reservation in this subnet.
+    duid.reset(new DUID(std::vector<uint8_t>(duid_vec.rbegin(),
+                                             duid_vec.rend())));
+    host = hosts_cfg->get6(542, duid);
+    ASSERT_TRUE(host);
+    resrv = host->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+    ASSERT_EQ(1, std::distance(resrv.first, resrv.second));
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_PD,
+                                            IOAddress("2001:db8:3:2::"),
+                                            96), resrv));
+
+    EXPECT_FALSE(hosts_cfg->get6(123, duid));
+    EXPECT_FALSE(hosts_cfg->get6(234, duid));
+}
+
+// This test verfies that the bogus host reservation would trigger a
+// server configuration error. In this case the "duid" parameter in the
+// reservation is misspelled.
+TEST_F(Dhcp6ParserTest, reservationBogus) {
+    ConstElementPtr x;
+    string config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ { "
+        "    \"pools\": [ ],"
+        "    \"subnet\": \"2001:db8:3::/64\", "
+        "    \"id\": 542,"
+        "    \"reservations\": ["
+        "      {"
+        "        \"dui\": \"0A:09:08:07:06:05:04:03:02:01\","
+        "        \"prefixes\": [ \"2001:db8:3:2::/96\" ],"
+        "        \"hostname\": \"\""
+        "      }"
+        "    ]"
+        " } ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"valid-lifetime\": 4000 }";
+
+    ElementPtr json = Element::fromJSON(config);
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+    checkResult(x, 1);
+}
+
 };

+ 6 - 0
src/lib/dhcpsrv/parsers/dhcp_parsers.cc

@@ -965,12 +965,18 @@ SubnetConfigParser::SubnetConfigParser(const std::string&,
 void
 SubnetConfigParser::build(ConstElementPtr subnet) {
     BOOST_FOREACH(ConfigPair param, subnet->mapValue()) {
+        // Host reservations must be parsed after subnet specific parameters.
+        if (param.first == "reservations") {
+            continue;
+        }
+
         ParserPtr parser;
         // When unsupported parameter is specified, the function called
         // below will thrown an exception. We have to catch this exception
         // to append the line number where the parameter is.
         try {
             parser.reset(createSubnetConfigParser(param.first));
+
         } catch (const std::exception& ex) {
             isc_throw(DhcpConfigError, ex.what() << " ("
                       << param.second->getPosition() << ")");

+ 1 - 1
src/lib/dhcpsrv/parsers/host_reservation_parser.cc

@@ -61,7 +61,7 @@ HostReservationParser::build(isc::data::ConstElementPtr reservation_data) {
     try {
         // hw-address or duid is a must.
         if (identifier_name.empty()) {
-            isc_throw(DhcpConfigError, "'hw-address' or 'duid' is a requirement"
+            isc_throw(DhcpConfigError, "'hw-address' or 'duid' is a required"
                       " parameter for host reservation");
         }
 

+ 7 - 2
src/lib/dhcpsrv/parsers/host_reservations_list_parser.h

@@ -15,6 +15,7 @@
 #ifndef HOST_RESERVATIONS_LIST_PARSER_H
 #define HOST_RESERVATIONS_LIST_PARSER_H
 
+#include <cc/data.h>
 #include <dhcpsrv/subnet_id.h>
 #include <dhcpsrv/parsers/dhcp_config_parser.h>
 #include <boost/foreach.hpp>
@@ -23,6 +24,11 @@ namespace isc {
 namespace dhcp {
 
 /// @brief Parser for a list of host reservations for a subnet.
+///
+/// @tparam HostReservationParserType Host reservation parser to be used to
+/// parse individual reservations: @c HostReservationParser4 or
+/// @c HostReservationParser6.
+template<typename HostReservationParserType>
 class HostReservationsListParser : public DhcpConfigParser {
 public:
 
@@ -41,9 +47,8 @@ public:
     ///
     /// @throw DhcpConfigError If the configuration if any of the reservations
     /// is invalid.
-    template<typename HostReservationParserType>
     virtual void build(isc::data::ConstElementPtr hr_list) {
-        BOOST_FOREACH(ConstElementPtr reservation, hr_list->listValue()) {
+        BOOST_FOREACH(data::ConstElementPtr reservation, hr_list->listValue()) {
             ParserPtr parser(new HostReservationParserType(subnet_id_));
             parser->build(reservation);
         }