Browse Source

[master] Merge branch 'trac3628'

Conflicts:
	src/lib/dhcpsrv/Makefile.am
	src/lib/dhcpsrv/cfg_hosts.cc
	src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc
Marcin Siodelski 10 years ago
parent
commit
00b49298ec
34 changed files with 1038 additions and 46 deletions
  1. 1 1
      src/bin/d2/d2_config.cc
  2. 1 1
      src/bin/d2/d2_config.h
  3. 1 1
      src/bin/d2/d_cfg_mgr.cc
  4. 1 1
      src/bin/d2/d_cfg_mgr.h
  5. 1 1
      src/bin/d2/tests/d_cfg_mgr_unittests.cc
  6. 40 3
      src/bin/dhcp4/dhcp4.spec
  7. 12 2
      src/bin/dhcp4/json_config_parser.cc
  8. 1 1
      src/bin/dhcp4/json_config_parser.h
  9. 206 0
      src/bin/dhcp4/tests/config_parser_unittest.cc
  10. 60 3
      src/bin/dhcp6/dhcp6.spec
  11. 12 3
      src/bin/dhcp6/json_config_parser.cc
  12. 1 1
      src/bin/dhcp6/json_config_parser.h
  13. 1 1
      src/bin/dhcp6/kea_controller.cc
  14. 243 0
      src/bin/dhcp6/tests/config_parser_unittest.cc
  15. 29 5
      src/lib/dhcpsrv/Makefile.am
  16. 21 1
      src/lib/dhcpsrv/cfg_hosts.cc
  17. 5 0
      src/lib/dhcpsrv/host.cc
  18. 5 0
      src/lib/dhcpsrv/host.h
  19. 1 1
      src/lib/dhcpsrv/dbaccess_parser.cc
  20. 2 2
      src/lib/dhcpsrv/dbaccess_parser.h
  21. 0 0
      src/lib/dhcpsrv/parsers/dhcp_config_parser.h
  22. 10 1
      src/lib/dhcpsrv/dhcp_parsers.cc
  23. 1 1
      src/lib/dhcpsrv/dhcp_parsers.h
  24. 2 2
      src/lib/dhcpsrv/host_reservation_parser.cc
  25. 1 1
      src/lib/dhcpsrv/host_reservation_parser.h
  26. 70 0
      src/lib/dhcpsrv/parsers/host_reservations_list_parser.h
  27. 1 0
      src/lib/dhcpsrv/tests/Makefile.am
  28. 14 8
      src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc
  29. 1 1
      src/lib/dhcpsrv/tests/cfgmgr_unittest.cc
  30. 1 2
      src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc
  31. 1 1
      src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc
  32. 110 1
      src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc
  33. 178 0
      src/lib/dhcpsrv/tests/host_reservations_list_parser_unittest.cc
  34. 4 0
      src/lib/dhcpsrv/tests/host_unittest.cc

+ 1 - 1
src/bin/d2/d2_config.cc

@@ -14,7 +14,7 @@
 
 #include <d2/d2_log.h>
 #include <d2/d2_cfg_mgr.h>
-#include <dhcpsrv/dhcp_parsers.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
 #include <exceptions/exceptions.h>
 #include <asiolink/io_error.h>
 

+ 1 - 1
src/bin/d2/d2_config.h

@@ -18,7 +18,7 @@
 #include <cc/data.h>
 #include <d2/d2_asio.h>
 #include <d2/d_cfg_mgr.h>
-#include <dhcpsrv/dhcp_parsers.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
 #include <dns/tsig.h>
 #include <exceptions/exceptions.h>
 

+ 1 - 1
src/bin/d2/d_cfg_mgr.cc

@@ -16,7 +16,7 @@
 #include <d2/d2_log.h>
 #include <dhcp/libdhcp++.h>
 #include <d2/d_cfg_mgr.h>
-#include <dhcpsrv/dhcp_parsers.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
 #include <util/encode/hex.h>
 #include <util/strutil.h>
 

+ 1 - 1
src/bin/d2/d_cfg_mgr.h

@@ -17,7 +17,7 @@
 
 #include <cc/data.h>
 #include <exceptions/exceptions.h>
-#include <dhcpsrv/dhcp_parsers.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
 
 #include <stdint.h>
 #include <string>

+ 1 - 1
src/bin/d2/tests/d_cfg_mgr_unittests.cc

@@ -14,7 +14,7 @@
 
 #include <config/ccsession.h>
 #include <config/module_spec.h>
-#include <dhcpsrv/dhcp_parsers.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
 #include <d2/d_cfg_mgr.h>
 #include <d_test_stubs.h>
 

+ 40 - 3
src/bin/dhcp4/dhcp4.spec

@@ -93,7 +93,7 @@
           { "item_name": "array",
             "item_type": "boolean",
             "item_optional": false,
-            "item_default": False
+            "item_default": false
           },
 
           { "item_name": "record-types",
@@ -147,7 +147,7 @@
           { "item_name": "csv-format",
             "item_type": "boolean",
             "item_optional": false,
-            "item_default": False
+            "item_default": false
           },
           { "item_name": "space",
             "item_type": "string",
@@ -316,7 +316,7 @@
                     { "item_name": "csv-format",
                       "item_type": "boolean",
                       "item_optional": false,
-                      "item_default": False
+                      "item_default": false
                       },
                     { "item_name": "space",
                       "item_type": "string",
@@ -324,6 +324,43 @@
                       "item_default": "dhcp4"
                     } ]
                   }
+                },
+                { "item_name": "reservations",
+                  "item_type": "list",
+                  "item_optional": false,
+                  "item_default": [],
+                  "list_item_spec":
+                  {
+                      "item_name": "reservation",
+                      "item_type": "map",
+                      "item_optional": false,
+                      "item_default": {},
+                      "map_item_spec": [
+                      {
+                        "item_name": "hw-address",
+                        "item_type": "string",
+                        "item_optional": true,
+                        "item_default": ""
+                      },
+                      {
+                        "item_name": "duid",
+                        "item_type": "string",
+                        "item_optional": true,
+                        "item_default": ""
+                      },
+                      {
+                        "item_name": "hostname",
+                        "item_type": "string",
+                        "item_optional": false,
+                        "item_default": ""
+                      },
+                      {
+                        "item_name": "ip-address",
+                        "item_type": "string",
+                        "item_optional": false,
+                        "item_default": "0.0.0.0"
+                      } ]
+                  }
                 } ]
          }
       },

+ 12 - 2
src/bin/dhcp4/json_config_parser.cc

@@ -19,9 +19,11 @@
 #include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcp4/json_config_parser.h>
-#include <dhcpsrv/dbaccess_parser.h>
-#include <dhcpsrv/dhcp_parsers.h>
 #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.

+ 1 - 1
src/bin/dhcp4/json_config_parser.h

@@ -13,8 +13,8 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <cc/data.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
 #include <exceptions/exceptions.h>
-#include <dhcpsrv/dhcp_parsers.h>
 
 #include <stdint.h>
 #include <string>

+ 206 - 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,209 @@ 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.
+TEST_F(Dhcp4ParserTest, reservationBogus) {
+    // Case 1: misspelled hw-address parameter.
+    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);
+
+    CfgMgr::instance().clear();
+
+    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+    checkResult(x, 1);
+
+    // Case 2: DUID and HW Address both specified.
+    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\": ["
+        "      {"
+        "        \"duid\": \"01:02:03:04:05:06\","
+        "        \"hw-address\": \"06:05:04:03:02:01\","
+        "        \"ip-address\": \"192.0.4.102\","
+        "        \"hostname\": \"\""
+        "      }"
+        "    ]"
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    json = Element::fromJSON(config);
+
+    // Remove existing configuration, if any.
+    CfgMgr::instance().clear();
+
+    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+    checkResult(x, 1);
+
+    // Case 3: Neither ip address nor hostname specified.
+    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-address\": \"06:05:04:03:02:01\""
+        "      }"
+        "    ]"
+        " } ],"
+        "\"valid-lifetime\": 4000 }";
+
+    json = Element::fromJSON(config);
+
+    // Remove existing configuration, if any.
+    CfgMgr::instance().clear();
+
+    EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
+    checkResult(x, 1);
+
+
+}
+
 }

+ 60 - 3
src/bin/dhcp6/dhcp6.spec

@@ -87,7 +87,7 @@
           { "item_name": "array",
             "item_type": "boolean",
             "item_optional": false,
-            "item_default": False
+            "item_default": false
           },
 
           { "item_name": "record-types",
@@ -141,7 +141,7 @@
           { "item_name": "csv-format",
             "item_type": "boolean",
             "item_optional": false,
-            "item_default": False
+            "item_default": false
           },
           { "item_name": "space",
             "item_type": "string",
@@ -354,7 +354,7 @@
                     { "item_name": "csv-format",
                       "item_type": "boolean",
                       "item_optional": false,
-                      "item_default": False
+                      "item_default": false
                     },
                     { "item_name": "space",
                       "item_type": "string",
@@ -362,6 +362,63 @@
                       "item_default": "dhcp6"
                     } ]
                   }
+                },
+                { "item_name": "reservations",
+                  "item_type": "list",
+                  "item_optional": false,
+                  "item_default": [],
+                  "list_item_spec":
+                  {
+                      "item_name": "reservation",
+                      "item_type": "map",
+                      "item_optional": false,
+                      "item_default": {},
+                      "map_item_spec": [
+                      {
+                        "item_name": "hw-address",
+                        "item_type": "string",
+                        "item_optional": true,
+                        "item_default": ""
+                      },
+                      {
+                        "item_name": "duid",
+                        "item_type": "string",
+                        "item_optional": true,
+                        "item_default": ""
+                      },
+                      {
+                        "item_name": "hostname",
+                        "item_type": "string",
+                        "item_optional": false,
+                        "item_default": ""
+                      },
+                      {
+                        "item_name": "ip-addresses",
+                        "item_type": "list",
+                        "item_optional": false,
+                        "item_default": [],
+                        "list_item_spec":
+                        {
+                            "item_name": "ip-address-reservation",
+                            "item_type": "string",
+                            "item_optional": false,
+                            "item_default": ""
+                        }
+                      },
+                      {
+                        "item_name": "prefixes",
+                        "item_type": "list",
+                        "item_optional": false,
+                        "item_default": [],
+                        "list_item_spec":
+                        {
+                            "item_name": "prefix-reservation",
+                            "item_type": "string",
+                            "item_optional": false,
+                            "item_default": ""
+                        }
+                      } ]
+                  }
                 } ]
             }
       },

+ 12 - 3
src/bin/dhcp6/json_config_parser.cc

@@ -21,12 +21,14 @@
 #include <dhcp/iface_mgr.h>
 #include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/cfgmgr.h>
-#include <dhcpsrv/dbaccess_parser.h>
-#include <dhcpsrv/dhcp_config_parser.h>
-#include <dhcpsrv/dhcp_parsers.h>
 #include <dhcpsrv/pool.h>
 #include <dhcpsrv/subnet.h>
 #include <dhcpsrv/triplet.h>
+#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);
+            }
         }
     }
 

+ 1 - 1
src/bin/dhcp6/json_config_parser.h

@@ -19,8 +19,8 @@
 /// DHCPv4 and DHCPv6. They should be merged. See ticket #2355.
 
 #include <cc/data.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
 #include <exceptions/exceptions.h>
-#include <dhcpsrv/dhcp_parsers.h>
 
 #include <string>
 

+ 1 - 1
src/bin/dhcp6/kea_controller.cc

@@ -15,8 +15,8 @@
 #include <config.h>
 
 #include <asiolink/asiolink.h>
-#include <dhcpsrv/dhcp_config_parser.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/parsers/dhcp_config_parser.h>
 #include <dhcp6/json_config_parser.h>
 #include <dhcp6/ctrl_dhcp6_srv.h>
 #include <dhcp6/dhcp6_log.h>

+ 243 - 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,246 @@ 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.
+TEST_F(Dhcp6ParserTest, reservationBogus) {
+    // Case 1: misspelled "duid" parameter.
+    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);
+
+    // Case 2: DUID and HW Address both specified.
+    config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ "
+        " { "
+        "    \"pools\": [ ],"
+        "    \"subnet\": \"2001:db8:3::/64\", "
+        "    \"id\": 542,"
+        "    \"reservations\": ["
+        "      {"
+        "        \"hw-address\": \"01:02:03:04:05:06\","
+        "        \"duid\": \"0A:09:08:07:06:05:04:03:02:01\","
+        "        \"prefixes\": [ \"2001:db8:3:2::/96\" ],"
+        "        \"hostname\": \"\""
+        "      }"
+        "    ]"
+        " } "
+        "], "
+        "\"preferred-lifetime\": 3000,"
+        "\"valid-lifetime\": 4000 }";
+
+    json = Element::fromJSON(config);
+
+    // Remove existing configuration, if any.
+    CfgMgr::instance().clear();
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+    checkResult(x, 1);
+
+    // Case 3: Neither ip address nor hostname specified.
+    config = "{ \"interfaces\": [ \"*\" ],"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [ "
+        " { "
+        "    \"pools\": [ ],"
+        "    \"subnet\": \"2001:db8:3::/64\", "
+        "    \"id\": 542,"
+        "    \"reservations\": ["
+        "      {"
+        "        \"duid\": \"0A:09:08:07:06:05:04:03:02:01\""
+        "      }"
+        "    ]"
+        " } "
+        "], "
+        "\"preferred-lifetime\": 3000,"
+        "\"valid-lifetime\": 4000 }";
+
+    json = Element::fromJSON(config);
+
+    // Remove existing configuration, if any.
+    CfgMgr::instance().clear();
+
+    EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
+    checkResult(x, 1);
+
+}
+
 };

+ 29 - 5
src/lib/dhcpsrv/Makefile.am

@@ -1,3 +1,5 @@
+AUTOMAKE_OPTIONS = subdir-objects
+
 SUBDIRS = . testutils tests
 
 dhcp_data_dir = @localstatedir@/@PACKAGE@
@@ -14,6 +16,21 @@ endif
 
 AM_CXXFLAGS = $(KEA_CXXFLAGS)
 
+# The files in the subfolder must be explicitly specified here so
+# as they are copied to the distribution. The other option would
+# be to specify a whole 'parsers' folder here but that would also
+# copy all other files, e.g. gitignore, .git etc.
+# Whenever new file is added to the parsers folder, it must be
+# added here.
+EXTRA_DIST =
+EXTRA_DIST += parsers/dbaccess_parser.cc
+EXTRA_DIST += parsers/dbaccess_parser.h
+EXTRA_DIST += parsers/dhcp_parsers.cc
+EXTRA_DIST += parsers/dhcp_parsers.h
+EXTRA_DIST += parsers/host_reservation_parser.cc
+EXTRA_DIST += parsers/host_reservation_parser.h
+EXTRA_DIST += parsers/host_reservations_list_parser.h
+
 # Define rule to build logging source files from message file
 dhcpsrv_messages.h dhcpsrv_messages.cc: s-messages
 
@@ -58,14 +75,10 @@ libkea_dhcpsrv_la_SOURCES += csv_lease_file6.cc csv_lease_file6.h
 libkea_dhcpsrv_la_SOURCES += d2_client_cfg.cc d2_client_cfg.h
 libkea_dhcpsrv_la_SOURCES += d2_client_mgr.cc d2_client_mgr.h
 libkea_dhcpsrv_la_SOURCES += daemon.cc daemon.h
-libkea_dhcpsrv_la_SOURCES += dbaccess_parser.cc dbaccess_parser.h
 libkea_dhcpsrv_la_SOURCES += dhcpsrv_log.cc dhcpsrv_log.h
-libkea_dhcpsrv_la_SOURCES += dhcp_config_parser.h
-libkea_dhcpsrv_la_SOURCES += dhcp_parsers.cc dhcp_parsers.h
 libkea_dhcpsrv_la_SOURCES += host.cc host.h
 libkea_dhcpsrv_la_SOURCES += host_container.h
 libkea_dhcpsrv_la_SOURCES += host_mgr.cc host_mgr.h
-libkea_dhcpsrv_la_SOURCES += host_reservation_parser.cc host_reservation_parser.h
 libkea_dhcpsrv_la_SOURCES += key_from_key.h
 libkea_dhcpsrv_la_SOURCES += lease.cc lease.h
 libkea_dhcpsrv_la_SOURCES += lease_mgr.cc lease_mgr.h
@@ -90,6 +103,17 @@ libkea_dhcpsrv_la_SOURCES += triplet.h
 libkea_dhcpsrv_la_SOURCES += utils.h
 libkea_dhcpsrv_la_SOURCES += writable_host_data_source.h
 
+# Configuration parsers
+libkea_dhcpsrv_la_SOURCES += parsers/dhcp_config_parser.h
+libkea_dhcpsrv_la_SOURCES += parsers/dbaccess_parser.cc
+libkea_dhcpsrv_la_SOURCES += parsers/dbaccess_parser.h
+libkea_dhcpsrv_la_SOURCES += parsers/dhcp_parsers.cc
+libkea_dhcpsrv_la_SOURCES += parsers/dhcp_parsers.h
+libkea_dhcpsrv_la_SOURCES += parsers/host_reservation_parser.cc
+libkea_dhcpsrv_la_SOURCES += parsers/host_reservation_parser.h
+libkea_dhcpsrv_la_SOURCES += parsers/host_reservations_list_parser.h
+
+
 nodist_libkea_dhcpsrv_la_SOURCES = dhcpsrv_messages.h dhcpsrv_messages.cc
 
 libkea_dhcpsrv_la_CXXFLAGS = $(AM_CXXFLAGS)
@@ -119,7 +143,7 @@ libkea_dhcpsrv_la_CXXFLAGS += -Wno-unused-parameter
 endif
 
 # The message file should be in the distribution
-EXTRA_DIST = dhcpsrv_messages.mes
+EXTRA_DIST += dhcpsrv_messages.mes
 
 # Distribute backend documentation
 # Database schema creation script moved to src/bin/admin

+ 21 - 1
src/lib/dhcpsrv/cfg_hosts.cc

@@ -14,6 +14,7 @@
 
 #include <dhcpsrv/cfg_hosts.h>
 #include <exceptions/exceptions.h>
+#include <ostream>
 
 using namespace isc::asiolink;
 
@@ -173,6 +174,23 @@ CfgHosts::add(const HostPtr& host) {
     /// @todo This may need further sanity checks.
     HWAddrPtr hwaddr = host->getHWAddress();
     DuidPtr duid = host->getDuid();
+
+    // There should be at least one resource reserved: hostname, IPv4
+    // address, IPv6 address or prefix.
+    if (host->getHostname().empty() &&
+        (host->getIPv4Reservation() == IOAddress("0.0.0.0")) &&
+        (!host->hasIPv6Reservation())) {
+        std::ostringstream s;
+        if (hwaddr) {
+            s << "for DUID: " << hwaddr->toText();
+        } else if (duid) {
+            s << "for HW address: " << duid->toText();
+        }
+        isc_throw(BadValue, "specified reservation " << s
+                  << " must include at least one resource, i.e. "
+                  "hostname, IPv4 address or IPv6 address/prefix");
+    }
+
     // Check for duplicates for the specified IPv4 subnet.
     if ((host->getIPv4SubnetID() > 0) &&
         get4(host->getIPv4SubnetID(), hwaddr, duid)) {
@@ -182,7 +200,7 @@ CfgHosts::add(const HostPtr& host) {
                   << "' to the IPv4 subnet id '" << host->getIPv4SubnetID()
                   << "' as this host has already been added");
 
-    // Checek for duplicates for the specified IPv6 subnet.
+    // Check for duplicates for the specified IPv6 subnet.
     } else if (host->getIPv6SubnetID() &&
                get6(host->getIPv6SubnetID(), duid, hwaddr)) {
         isc_throw(DuplicateHost, "failed to add new host using the HW"
@@ -192,6 +210,8 @@ CfgHosts::add(const HostPtr& host) {
                   << "' as this host has already been added");
     }
 
+    /// @todo This may need further sanity checks.
+
     // This is a new instance - add it.
     hosts_.insert(host);
 }

+ 5 - 0
src/lib/dhcpsrv/host.cc

@@ -195,6 +195,11 @@ Host::getIPv6Reservations(const IPv6Resrv::Type& type) const {
 }
 
 bool
+Host::hasIPv6Reservation() const {
+    return (!ipv6_reservations_.empty());
+}
+
+bool
 Host::hasReservation(const IPv6Resrv& reservation) const {
     IPv6ResrvRange reservations = getIPv6Reservations(reservation.getType());
     if (std::distance(reservations.first, reservations.second) > 0) {

+ 5 - 0
src/lib/dhcpsrv/host.h

@@ -352,6 +352,11 @@ public:
     /// the specified type.
     IPv6ResrvRange getIPv6Reservations(const IPv6Resrv::Type& type) const;
 
+    /// @brief Checks if there is at least one IPv6 reservation for this host.
+    ///
+    /// @return true if there is a reservation for the host, false otherwise.
+    bool hasIPv6Reservation() const;
+
     /// @brief Checks if specified IPv6 reservation exists for the host.
     ///
     /// @param reservation A reservation to be checked for the host.

+ 1 - 1
src/lib/dhcpsrv/dbaccess_parser.cc

@@ -13,9 +13,9 @@
 // PERFORMANCE OF THIS SOFTWARE.
 
 #include <dhcp/option.h>
-#include <dhcpsrv/dbaccess_parser.h>
 #include <dhcpsrv/dhcpsrv_log.h>
 #include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/parsers/dbaccess_parser.h>
 
 #include <boost/foreach.hpp>
 

+ 2 - 2
src/lib/dhcpsrv/dbaccess_parser.h

@@ -16,8 +16,8 @@
 #define DBACCESS_PARSER_H
 
 #include <cc/data.h>
-#include <dhcpsrv/dhcp_config_parser.h>
-#include <dhcpsrv/dhcp_parsers.h>
+#include <dhcpsrv/parsers/dhcp_config_parser.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
 #include <exceptions/exceptions.h>
 
 #include <string>

src/lib/dhcpsrv/dhcp_config_parser.h → src/lib/dhcpsrv/parsers/dhcp_config_parser.h


+ 10 - 1
src/lib/dhcpsrv/dhcp_parsers.cc

@@ -16,7 +16,7 @@
 #include <dhcp/libdhcp++.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfg_option.h>
-#include <dhcpsrv/dhcp_parsers.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
 #include <hooks/hooks_manager.h>
 #include <util/encode/hex.h>
 #include <util/strutil.h>
@@ -1038,12 +1038,21 @@ 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.
+        // Note that the reservation parsing will be invoked by the build()
+        // in the derived classes, i.e. Subnet4ConfigParser and
+        // Subnet6ConfigParser.
+        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/dhcp_parsers.h

@@ -19,11 +19,11 @@
 #include <cc/data.h>
 #include <dhcp/option_definition.h>
 #include <dhcpsrv/d2_client_cfg.h>
-#include <dhcpsrv/dhcp_config_parser.h>
 #include <dhcpsrv/cfg_iface.h>
 #include <dhcpsrv/cfg_option.h>
 #include <dhcpsrv/option_space_container.h>
 #include <dhcpsrv/subnet.h>
+#include <dhcpsrv/parsers/dhcp_config_parser.h>
 #include <exceptions/exceptions.h>
 #include <util/optional_value.h>
 

+ 2 - 2
src/lib/dhcpsrv/host_reservation_parser.cc

@@ -14,7 +14,7 @@
 
 #include <asiolink/io_address.h>
 #include <dhcpsrv/cfgmgr.h>
-#include <dhcpsrv/host_reservation_parser.h>
+#include <dhcpsrv/parsers/host_reservation_parser.h>
 #include <boost/foreach.hpp>
 #include <boost/lexical_cast.hpp>
 #include <string>
@@ -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");
         }
 

+ 1 - 1
src/lib/dhcpsrv/host_reservation_parser.h

@@ -16,8 +16,8 @@
 #define HOST_RESERVATION_PARSER_H
 
 #include <cc/data.h>
-#include <dhcpsrv/dhcp_config_parser.h>
 #include <dhcpsrv/host.h>
+#include <dhcpsrv/parsers/dhcp_config_parser.h>
 
 namespace isc {
 namespace dhcp {

+ 70 - 0
src/lib/dhcpsrv/parsers/host_reservations_list_parser.h

@@ -0,0 +1,70 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#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>
+
+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:
+
+    /// @brief Constructor.
+    ///
+    /// @param subnet_id Identifier of the subnet to which the reservations
+    /// belong.
+    HostReservationsListParser(const SubnetID& subnet_id)
+        : subnet_id_(subnet_id) {
+    }
+
+    /// @brief Parses a list of host reservation entries for a subnet.
+    ///
+    /// @param hr_list Data element holding a list of host reservations.
+    /// Each host reservation is described by a map object.
+    ///
+    /// @throw DhcpConfigError If the configuration if any of the reservations
+    /// is invalid.
+    virtual void build(isc::data::ConstElementPtr hr_list) {
+        BOOST_FOREACH(data::ConstElementPtr reservation, hr_list->listValue()) {
+            ParserPtr parser(new HostReservationParserType(subnet_id_));
+            parser->build(reservation);
+        }
+    }
+
+    /// @brief Commit, unused.
+    virtual void commit() { }
+
+private:
+
+    /// @brief Identifier of the subnet to whic the reservations belong.
+    SubnetID subnet_id_;
+
+};
+
+}
+}
+
+#endif // HOST_RESERVATIONS_LIST_PARSER_H

+ 1 - 0
src/lib/dhcpsrv/tests/Makefile.am

@@ -71,6 +71,7 @@ libdhcpsrv_unittests_SOURCES += dbaccess_parser_unittest.cc
 libdhcpsrv_unittests_SOURCES += host_mgr_unittest.cc
 libdhcpsrv_unittests_SOURCES += host_unittest.cc
 libdhcpsrv_unittests_SOURCES += host_reservation_parser_unittest.cc
+libdhcpsrv_unittests_SOURCES += host_reservations_list_parser_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_file_io.cc lease_file_io.h
 libdhcpsrv_unittests_SOURCES += lease_unittest.cc
 libdhcpsrv_unittests_SOURCES += lease_mgr_factory_unittest.cc

+ 14 - 8
src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc

@@ -352,53 +352,59 @@ TEST_F(CfgHostsTest, duplicatesSubnet4DUID) {
 }
 
 // This test verifies that it is not possible to add the same Host to the
-// same IPv4 subnet twice.
+// same IPv6 subnet twice.
 TEST_F(CfgHostsTest, duplicatesSubnet6HWAddr) {
     CfgHosts cfg;
     // Add a host.
     ASSERT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
                                              "hw-address",
                                              SubnetID(0), SubnetID(1),
-                                             IOAddress("0.0.0.0")))));
+                                             IOAddress("0.0.0.0"),
+                                             "foo.example.com"))));
 
     // Try to add the host with the same HW address to the same subnet. The fact
     // that the IP address is different here shouldn't really matter.
     EXPECT_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
                                           "hw-address",
                                           SubnetID(0), SubnetID(1),
-                                          IOAddress("0.0.0.0")))),
+                                          IOAddress("0.0.0.0"),
+                                          "foo.example.com"))),
                  isc::dhcp::DuplicateHost);
 
     // Now try to add it to a different subnet. It should go through.
     EXPECT_NO_THROW(cfg.add(HostPtr(new Host(hwaddrs_[0]->toText(false),
                                              "hw-address",
                                              SubnetID(0), SubnetID(2),
-                                             IOAddress("0.0.0.0")))));
+                                             IOAddress("0.0.0.0"),
+                                             "foo.example.com"))));
 }
 
 // This test verifies that it is not possible to add the same Host to the
-// same IPv4 subnet twice.
+// same IPv6 subnet twice.
 TEST_F(CfgHostsTest, duplicatesSubnet6DUID) {
     CfgHosts cfg;
     // Add a host.
     ASSERT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
                                              "duid",
                                              SubnetID(0), SubnetID(1),
-                                             IOAddress("0.0.0.0")))));
+                                             IOAddress("0.0.0.0"),
+                                             "foo.example.com"))));
 
     // Try to add the host with the same DUID to the same subnet. The fact
     // that the IP address is different here shouldn't really matter.
     EXPECT_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
                                           "duid",
                                           SubnetID(0), SubnetID(1),
-                                          IOAddress("0.0.0.0")))),
+                                          IOAddress("0.0.0.0"),
+                                          "foo.example.com"))),
                  isc::dhcp::DuplicateHost);
 
     // Now try to add it to a different subnet. It should go through.
     EXPECT_NO_THROW(cfg.add(HostPtr(new Host(duids_[0]->toText(),
                                              "duid",
                                              SubnetID(0), SubnetID(2),
-                                             IOAddress("0.0.0.0")))));
+                                             IOAddress("0.0.0.0"),
+                                             "foo.example.com"))));
 }
 
 

+ 1 - 1
src/lib/dhcpsrv/tests/cfgmgr_unittest.cc

@@ -18,8 +18,8 @@
 #include <dhcp/dhcp6.h>
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/cfgmgr.h>
-#include <dhcpsrv/dhcp_parsers.h>
 #include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
 
 #include <gtest/gtest.h>
 

+ 1 - 2
src/lib/dhcpsrv/tests/dbaccess_parser_unittest.cc

@@ -15,9 +15,8 @@
 #include <config.h>
 
 #include <config/ccsession.h>
-#include <dhcpsrv/dbaccess_parser.h>
-#include <dhcpsrv/dhcp_parsers.h>
 #include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/parsers/dbaccess_parser.h>
 #include <log/logger_support.h>
 
 #include <gtest/gtest.h>

+ 1 - 1
src/lib/dhcpsrv/tests/dhcp_parsers_unittest.cc

@@ -22,7 +22,7 @@
 #include <dhcp/tests/iface_mgr_test_config.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/subnet.h>
-#include <dhcpsrv/dhcp_parsers.h>
+#include <dhcpsrv/parsers/dhcp_parsers.h>
 #include <dhcpsrv/tests/test_libraries.h>
 #include <dhcpsrv/testutils/config_result_check.h>
 #include <exceptions/exceptions.h>

+ 110 - 1
src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc

@@ -20,7 +20,7 @@
 #include <dhcp/hwaddr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/host.h>
-#include <dhcpsrv/host_reservation_parser.h>
+#include <dhcpsrv/parsers/host_reservation_parser.h>
 #include <dhcpsrv/testutils/config_result_check.h>
 #include <gtest/gtest.h>
 #include <iterator>
@@ -141,6 +141,30 @@ TEST_F(HostReservationParserTest, dhcp4DUID) {
     EXPECT_TRUE(hosts[0]->getHostname().empty());
 }
 
+// This test verifies that the parser can parse the reservation entry
+// when IPv4 address is specified, but hostname is not.
+TEST_F(HostReservationParserTest, dhcp4NoHostname) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"ip-address\": \"192.0.2.10\" }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    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(HWAddrPtr(), duid_));
+
+    ASSERT_EQ(1, hosts.size());
+
+    EXPECT_EQ(10, hosts[0]->getIPv4SubnetID());
+    EXPECT_EQ(0, hosts[0]->getIPv6SubnetID());
+    EXPECT_EQ("192.0.2.10", hosts[0]->getIPv4Reservation().toText());
+    EXPECT_TRUE(hosts[0]->getHostname().empty());
+}
+
+
 // This test verifies that the configuration parser for host reservations
 // throws an exception when IPv6 address is specified for IPv4 address
 // reservation.
@@ -166,6 +190,53 @@ TEST_F(HostReservationParserTest, noIdentifier) {
     EXPECT_THROW(parser.build(config_element), DhcpConfigError);
 }
 
+// This test verifies  that the configuration parser for host reservations
+// throws an exception when neither ip address nor hostname is specified.
+TEST_F(HostReservationParserTest, noResource) {
+    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\" }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser4 parser(SubnetID(10));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
+// This test verifies that the parser can parse the reservation entry
+// when IP address is not specified, but hostname is specified.
+TEST_F(HostReservationParserTest, noIPAddress) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"hostname\": \"foo.example.com\" }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    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(HWAddrPtr(), duid_));
+
+    ASSERT_EQ(1, hosts.size());
+
+    EXPECT_EQ(10, hosts[0]->getIPv4SubnetID());
+    EXPECT_EQ(0, hosts[0]->getIPv6SubnetID());
+    EXPECT_EQ("0.0.0.0", hosts[0]->getIPv4Reservation().toText());
+    EXPECT_EQ("foo.example.com", hosts[0]->getHostname());
+}
+
+// This test verifies  that the configuration parser for host reservations
+// throws an exception when hostname is empty, and IP address is not
+// specified.
+TEST_F(HostReservationParserTest, emptyHostname) {
+    std::string config = "{ \"hw-address\": \"01:02:03:04:05:06\","
+        "\"hostname\": \"\" }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser4 parser(SubnetID(10));
+    EXPECT_THROW(parser.build(config_element), DhcpConfigError);
+}
+
 // This test verifies that the configuration parser for host reservations
 // throws an exception when invalid IP address is specified.
 TEST_F(HostReservationParserTest, malformedAddress) {
@@ -264,6 +335,44 @@ TEST_F(HostReservationParserTest, dhcp6DUID) {
     ASSERT_EQ(0, std::distance(prefixes.first, prefixes.second));
 }
 
+// This test verfies that the parser can parse the IPv6 reservation entry
+// which lacks hostname parameter.
+TEST_F(HostReservationParserTest, dhcp6NoHostname) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"ip-addresses\": [ \"2001:db8:1::100\", \"2001:db8:1::200\" ],"
+        "\"prefixes\": [ ] }";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationParser6 parser(SubnetID(12));
+    ASSERT_NO_THROW(parser.build(config_element));
+
+    CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+    HostCollection hosts;
+    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_));
+
+    ASSERT_EQ(1, hosts.size());
+
+    EXPECT_EQ(0, hosts[0]->getIPv4SubnetID());
+    EXPECT_EQ(12, hosts[0]->getIPv6SubnetID());
+    EXPECT_TRUE(hosts[0]->getHostname().empty());
+
+    IPv6ResrvRange addresses = hosts[0]->
+        getIPv6Reservations(IPv6Resrv::TYPE_NA);
+    ASSERT_EQ(2, std::distance(addresses.first, addresses.second));
+
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                            IOAddress("2001:db8:1::100")),
+                                  addresses));
+    EXPECT_TRUE(reservationExists(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                            IOAddress("2001:db8:1::200")),
+                                  addresses));
+
+    IPv6ResrvRange prefixes = hosts[0]->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+    ASSERT_EQ(0, std::distance(prefixes.first, prefixes.second));
+}
+
+
 // This test verifies that the configuration parser throws an exception
 // when IPv4 address is specified for IPv6 reservation.
 TEST_F(HostReservationParserTest, dhcp6IPv4Address) {

+ 178 - 0
src/lib/dhcpsrv/tests/host_reservations_list_parser_unittest.cc

@@ -0,0 +1,178 @@
+// Copyright (C) 2014 Internet Systems Consortium, Inc. ("ISC")
+//
+// Permission to use, copy, modify, and/or distribute this software for any
+// purpose with or without fee is hereby granted, provided that the above
+// copyright notice and this permission notice appear in all copies.
+//
+// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+// PERFORMANCE OF THIS SOFTWARE.
+
+#include <config.h>
+
+#include <cc/data.h>
+#include <dhcp/duid.h>
+#include <dhcp/hwaddr.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_hosts.h>
+#include <dhcpsrv/host.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/parsers/host_reservation_parser.h>
+#include <dhcpsrv/parsers/host_reservations_list_parser.h>
+#include <gtest/gtest.h>
+
+using namespace isc::data;
+using namespace isc::dhcp;
+
+namespace {
+
+/// @brief Test fixture class for @c HostReservationsListParser.
+class HostReservationsListParserTest : public ::testing::Test {
+protected:
+
+    /// @brief Setup for each test.
+    ///
+    /// Clears the configuration in the @c CfgMgr. It alse initializes
+    /// hwaddr_ and duid_ class members.
+    virtual void SetUp();
+
+    /// @brief Clean up after each test.
+    ///
+    /// Clears the configuration in the @c CfgMgr.
+    virtual void TearDown();
+
+    /// @brief HW Address object used by tests.
+    HWAddrPtr hwaddr_;
+
+    /// @brief DUID object used by tests.
+    DuidPtr duid_;
+};
+
+void
+HostReservationsListParserTest::SetUp() {
+    CfgMgr::instance().clear();
+    // Initialize HW address used by tests.
+    const uint8_t hwaddr_data[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06 };
+    hwaddr_ = HWAddrPtr(new HWAddr(hwaddr_data, sizeof(hwaddr_data),
+                                   HTYPE_ETHER));
+
+    // Initialize DUID used by tests.
+    const uint8_t duid_data[] = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
+                                  0x08, 0x09, 0x0A };
+    duid_ = DuidPtr(new DUID(duid_data, sizeof(duid_data)));
+}
+
+void
+HostReservationsListParserTest::TearDown() {
+    CfgMgr::instance().clear();
+}
+
+// This test verifies that the parser for the list of the host reservations
+// parses IPv4 reservations correctly.
+TEST_F(HostReservationsListParserTest, ipv4Reservations) {
+    std::string config =
+        "[ "
+        "  { "
+        "   \"hw-address\": \"01:02:03:04:05:06\","
+        "   \"ip-address\": \"192.0.2.134\","
+        "   \"hostname\": \"foo.example.com\""
+        "  }, "
+        "  { "
+        "   \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "   \"ip-address\": \"192.0.2.110\","
+        "   \"hostname\": \"bar.example.com\""
+        "  } "
+        "]";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    HostReservationsListParser<HostReservationParser4> parser(SubnetID(1));
+    ASSERT_NO_THROW(parser.build(config_element));
+
+    CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+    HostCollection hosts;
+
+    // Get the first reservation for the host identified by the HW address.
+    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(hwaddr_));
+    ASSERT_EQ(1, hosts.size());
+
+    EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+    EXPECT_EQ(0, hosts[0]->getIPv6SubnetID());
+    EXPECT_EQ("192.0.2.134", hosts[0]->getIPv4Reservation().toText());
+    EXPECT_EQ("foo.example.com", hosts[0]->getHostname());
+
+    // Get the second reservation for the host identified by the DUID.
+    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_));
+    ASSERT_EQ(1, hosts.size());
+
+    EXPECT_EQ(1, hosts[0]->getIPv4SubnetID());
+    EXPECT_EQ(0, hosts[0]->getIPv6SubnetID());
+    EXPECT_EQ("192.0.2.110", hosts[0]->getIPv4Reservation().toText());
+    EXPECT_EQ("bar.example.com", hosts[0]->getHostname());
+}
+
+// This test verifies that the parser for the list of the host reservations
+// parses IPv6 reservations correctly.
+TEST_F(HostReservationsListParserTest, ipv6Reservations) {
+    std::string config = 
+        "[ "
+        "  { \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "    \"ip-addresses\": [ ],"
+        "    \"prefixes\": [ \"2001:db8:1:2::/80\" ],"
+        "    \"hostname\": \"foo.example.com\" "
+        "  }, "
+        "  { \"hw-address\": \"01:02:03:04:05:06\","
+        "    \"ip-addresses\": [ \"2001:db8:1::123\" ],"
+        "    \"prefixes\": [ ],"
+        "    \"hostname\": \"bar.example.com\" "
+        "  } "
+        "]";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    // Parse configuration.
+    HostReservationsListParser<HostReservationParser6> parser(SubnetID(2));
+    ASSERT_NO_THROW(parser.build(config_element));
+
+    CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+    HostCollection hosts;
+
+    // Get the reservation for the host identified by the HW address.
+    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(hwaddr_));
+    ASSERT_EQ(1, hosts.size());
+
+    // Make sure it belongs to a valid subnet.
+    EXPECT_EQ(0, hosts[0]->getIPv4SubnetID());
+    EXPECT_EQ(2, hosts[0]->getIPv6SubnetID());
+
+    // Get the reserved addresses for the host. There should be exactly one
+    // address reserved for this host.
+    IPv6ResrvRange prefixes =
+        hosts[0]->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+    ASSERT_EQ(1, std::distance(prefixes.first, prefixes.second));
+
+    EXPECT_EQ(IPv6Resrv::TYPE_NA, prefixes.first->second.getType());
+    EXPECT_EQ("2001:db8:1::123", prefixes.first->second.getPrefix().toText());
+    EXPECT_EQ(128, prefixes.first->second.getPrefixLen());
+
+    // Validate the second reservation.
+    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(HWAddrPtr(), duid_));
+    ASSERT_EQ(1, hosts.size());
+
+    EXPECT_EQ(0, hosts[0]->getIPv4SubnetID());
+    EXPECT_EQ(2, hosts[0]->getIPv6SubnetID());
+
+    // This reservation was for a prefix, instead of an IPv6 address.
+    prefixes = hosts[0]->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+    ASSERT_EQ(1, std::distance(prefixes.first, prefixes.second));
+
+    EXPECT_EQ(IPv6Resrv::TYPE_PD, prefixes.first->second.getType());
+    EXPECT_EQ("2001:db8:1:2::", prefixes.first->second.getPrefix().toText());
+    EXPECT_EQ(80, prefixes.first->second.getPrefixLen());
+}
+
+} // end of anonymous namespace

+ 4 - 0
src/lib/dhcpsrv/tests/host_unittest.cc

@@ -353,6 +353,8 @@ TEST(HostTest, addReservations) {
                                         SubnetID(1), SubnetID(2),
                                         IOAddress("192.0.2.3"))));
 
+    EXPECT_FALSE(host->hasIPv6Reservation());
+
     // Add 4 reservations: 2 for NAs, 2 for PDs.
     ASSERT_NO_THROW(
         host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
@@ -365,6 +367,8 @@ TEST(HostTest, addReservations) {
                                        IOAddress("2001:db8:1::1")));
     );
 
+    EXPECT_TRUE(host->hasIPv6Reservation());
+
     // Check that reservations exist.
     EXPECT_TRUE(host->hasReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
                                                IOAddress("2001:db8:1::cafe"))));