]> git.ipfire.org Git - thirdparty/kea.git/commitdiff
[#1387] Checkpoint: added PDExclude UTs
authorFrancis Dupont <fdupont@isc.org>
Mon, 1 Jul 2024 15:51:37 +0000 (17:51 +0200)
committerFrancis Dupont <fdupont@isc.org>
Wed, 4 Sep 2024 13:09:40 +0000 (15:09 +0200)
src/lib/dhcpsrv/parsers/host_reservation_parser.cc
src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc
src/lib/dhcpsrv/tests/host_reservations_list_parser_unittest.cc

index 79a9b9d96075ebddf924afb3be10e8447276ae6c..470ec395546feb9c0a509a4aab6efae9a4fb5cde 100644 (file)
@@ -86,6 +86,7 @@ getSupportedParams6(const bool identifiers_only = false) {
         params_set.insert("hostname");
         params_set.insert("ip-addresses");
         params_set.insert("prefixes");
+        params_set.insert("excluded-prefixes");
         params_set.insert("option-data");
         params_set.insert("client-classes");
         params_set.insert("user-context");
@@ -246,6 +247,63 @@ HostReservationParser4::getSupportedParameters(const bool identifiers_only) cons
     return (getSupportedParams4(identifiers_only));
 }
 
+namespace {
+
+// Extract the prefix length from the value specified in
+// the following format: 2001:db8:2000::/64.
+std::pair<IOAddress, uint8_t>
+parsePrefix(std::string prefix, std::string msg) {
+    uint8_t prefix_len = 128;
+    // The slash is mandatory for prefixes. If there is no slash,
+    // return an error.
+    size_t len_pos  = prefix.find('/');
+    if (len_pos == std::string::npos) {
+        isc_throw(DhcpConfigError, msg << " requires prefix length "
+                  << "be specified in '" << prefix << "'");
+
+        // If there is nothing after the slash, we should also
+        // report an error.
+    } else if (len_pos >= prefix.length() - 1) {
+        isc_throw(DhcpConfigError, "prefix '" <<  prefix
+                  << "' requires length after '/'");
+
+    }
+
+    // Convert the prefix length from the string to the number.
+    // Note, that we don't use the uint8_t type as the lexical cast
+    // would expect a character, e.g. 'a', instead of prefix length,
+    // e.g. '64'.
+
+    try {
+        prefix_len = boost::lexical_cast<unsigned int>(prefix.substr(len_pos + 1));
+    } catch (const boost::bad_lexical_cast&) {
+        isc_throw(DhcpConfigError, "prefix length value '"
+                  << prefix.substr(len_pos + 1) << "' is invalid");
+    }
+
+    if ((prefix_len == 0) || (prefix_len > 128)) {
+        isc_throw(OutOfRange,
+                  "'prefix-len' value must be in range of [1..128]");
+    }
+
+    // Remove the slash character and the prefix length from the
+    // parsed value.
+    prefix.erase(len_pos);
+    IOAddress addr(prefix);
+
+    if (prefix_len != 128) {
+        IOAddress first_address = firstAddrInPrefix(addr, prefix_len);
+        if (first_address != addr) {
+            isc_throw(BadValue, "Prefix address: " << addr
+                      << " exceeds prefix/prefix-len pair: " << first_address
+                      << "/" << static_cast<uint32_t>(prefix_len));
+        }
+    }
+    return (std::pair<IOAddress, uint8_t>(addr, prefix_len));
+}
+
+} // end of anonymous namespace.
+
 HostPtr
 HostReservationParser6::parseInternal(const SubnetID& subnet_id,
                                       isc::data::ConstElementPtr reservation_data,
@@ -255,107 +313,91 @@ HostReservationParser6::parseInternal(const SubnetID& subnet_id,
 
     host->setIPv6SubnetID(subnet_id);
 
-    for (auto const& element : reservation_data->mapValue()) {
-        // Parse option values. Note that the configuration option parser
-        // returns errors with position information appended, so there is no
-        // need to surround it with try-clause (and rethrow with position
-        // appended).
-        if (element.first == "option-data") {
-            CfgOptionPtr cfg_option = host->getCfgOption6();
-
-            // This parser is converted to SimpleParser already. It
-            // parses the Element structure immediately, there's no need
-            // to go through build/commit phases.
-            OptionDataListParser parser(AF_INET6);
-            parser.parse(cfg_option, element.second, encapsulate_options);
-
-        } else if (element.first == "ip-addresses" || element.first == "prefixes") {
-            for (auto const& prefix_element : element.second->listValue()) {
-                try {
-                    // For the IPv6 address the prefix length is 128 and the
-                    // value specified in the list is a reserved address.
-                    IPv6Resrv::Type resrv_type = IPv6Resrv::TYPE_NA;
-                    std::string prefix = prefix_element->stringValue();
-                    uint8_t prefix_len = 128;
-
-                    // If we're dealing with prefixes, instead of addresses,
-                    // we will have to extract the prefix length from the value
-                    // specified in the following format: 2001:db8:2000::/64.
-                    if (element.first == "prefixes") {
-                        // The slash is mandatory for prefixes. If there is no
-                        // slash, return an error.
-                        size_t len_pos  = prefix.find('/');
-                        if (len_pos == std::string::npos) {
-                            isc_throw(DhcpConfigError, "prefix reservation"
-                                      " requires prefix length be specified"
-                                      " in '" << prefix << "'");
-
-                        // If there is nothing after the slash, we should also
-                        // report an error.
-                        } else if (len_pos >= prefix.length() - 1) {
-                            isc_throw(DhcpConfigError, "prefix '" <<  prefix
-                                      << "' requires length after '/'");
-
-                        }
-
-                        // Convert the prefix length from the string to the
-                        // number. Note, that we don't use the uint8_t type
-                        // as the lexical cast would expect a character, e.g.
-                        // 'a', instead of prefix length, e.g. '64'.
-                        try {
-                            prefix_len = boost::lexical_cast<unsigned int>(prefix.substr(len_pos + 1));
-
-                        } catch (const boost::bad_lexical_cast&) {
-                            isc_throw(DhcpConfigError, "prefix length value '"
-                                      << prefix.substr(len_pos + 1)
-                                      << "' is invalid");
-                        }
-
-                        if ((prefix_len == 0) || (prefix_len > 128)) {
-                            isc_throw(OutOfRange,
-                                      "'prefix-len' value must be in range of [1..128]");
-                        }
-
-                        // Remove the slash character and the prefix length
-                        // from the parsed value.
-                        prefix.erase(len_pos);
-
-                        // Finally, set the reservation type.
-                        resrv_type = IPv6Resrv::TYPE_PD;
-
-                        if (prefix_len != 128) {
-                            IOAddress addr(prefix);
-                            IOAddress first_address = firstAddrInPrefix(addr, prefix_len);
-                            if (first_address != addr) {
-                                isc_throw(BadValue, "Prefix address: " << addr
-                                          << " exceeds prefix/prefix-len pair: " << first_address
-                                          << "/" << static_cast<uint32_t>(prefix_len));
-                            }
-                        }
-                    }
-
-                    // Create a reservation for an address or prefix.
-                    host->addReservation(IPv6Resrv(resrv_type,
-                                                   IOAddress(prefix),
-                                                   prefix_len));
+    ConstElementPtr option_data = reservation_data->get("option-data");
+    ConstElementPtr ip_addresses = reservation_data->get("ip-addresses");
+    ConstElementPtr prefixes = reservation_data->get("prefixes");
+    ConstElementPtr excluded_prefixes = reservation_data->get("excluded-prefixes");
+    ConstElementPtr client_classes = reservation_data->get("client-classes");
+
+    // Parse option values. Note that the configuration option parser
+    // returns errors with position information appended, so there is no
+    // need to surround it with try-clause (and rethrow with position
+    // appended).
+    if (option_data) {
+        CfgOptionPtr cfg_option = host->getCfgOption6();
+
+        // This parser is converted to SimpleParser already. It
+        // parses the Element structure immediately, there's no need
+        // to go through build/commit phases.
+        OptionDataListParser parser(AF_INET6);
+        parser.parse(cfg_option, option_data, encapsulate_options);
+    }
 
-                } catch (const std::exception& ex) {
-                    // Append line number where the error occurred.
-                    isc_throw(DhcpConfigError, ex.what() << " ("
-                              << prefix_element->getPosition() << ")");
-                }
+    if (ip_addresses) {
+        for (size_t idx = 0; idx < ip_addresses->size(); ++idx) {
+            ConstElementPtr element = ip_addresses->get(idx);
+            try {
+                // For the IPv6 address the prefix length is 128 and the
+                // value specified in the list is a reserved address.
+                std::string addr = element->stringValue();
+                // Create a reservation for the address.
+                host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                               IOAddress(addr), 128));
+            } catch (const std::exception& ex) {
+                // Append line number where the error occurred.
+                isc_throw(DhcpConfigError, ex.what() << " ("
+                          << element->getPosition() << ")");
             }
+        }
+    }
+
+    if (excluded_prefixes) {
+        if (!prefixes) {
+            isc_throw(DhcpConfigError, "'excluded-prefixes' parameter "
+                      "requires the 'prefixes' parameter");
+        }
+        if (excluded_prefixes->size() != prefixes->size()) {
+            isc_throw(DhcpConfigError, "'excluded-prefixes' parameter "
+                      "does not match the 'prefixes' parameter: "
+                      << excluded_prefixes->size() << " != "
+                      << prefixes->size());
+        }
+    }
 
-        } else if (element.first == "client-classes") {
+    if (prefixes) {
+        for (size_t idx = 0; idx < prefixes->size(); ++idx) {
+            ConstElementPtr element = prefixes->get(idx);
             try {
-                for (auto const& class_element : element.second->listValue()) {
-                    host->addClientClass6(class_element->stringValue());
+                std::string prefix = element->stringValue();
+                auto const p = parsePrefix(prefix, "prefix reservation");
+                IPv6Resrv res(IPv6Resrv::TYPE_PD, p.first, p.second);
+                std::string exclude("");
+                if (excluded_prefixes) {
+                    element = excluded_prefixes->get(idx);
+                    exclude = element->stringValue();
+                }
+                if (!exclude.empty()) {
+                    auto const x = parsePrefix(exclude, "exclude prefix");
+                    res.setPDExclude(x.first, x.second);
                 }
+                host->addReservation(res);
             } catch (const std::exception& ex) {
                 // Append line number where the error occurred.
                 isc_throw(DhcpConfigError, ex.what() << " ("
-                          << element.second->getPosition() << ")");
+                          << element->getPosition() << ")");
+            }
+        }
+    }
+
+    if (client_classes) {
+        try {
+            for (auto const& class_element : client_classes->listValue()) {
+                host->addClientClass6(class_element->stringValue());
             }
+        } catch (const std::exception& ex) {
+            // Append line number where the error occurred.
+            isc_throw(DhcpConfigError, ex.what() << " ("
+                      << client_classes->getPosition() << ")");
         }
     }
 
index b9c6ad249858afebcd1bf1e774c434a219c0e21a..115d323b868c454b02f4983fc4f0d335afac822a 100644 (file)
@@ -264,6 +264,10 @@ CfgHostsSubnet::toElement() const {
         if (prefixes && prefixes->empty()) {
             resv->remove("prefixes");
         }
+        ConstElementPtr excluded_prefixes = resv->get("excluded-prefixes");
+        if (excluded_prefixes && excluded_prefixes->empty()) {
+            resv->remove("excluded-prefixes");
+        }
         ConstElementPtr hostname = resv->get("hostname");
         if (hostname && hostname->stringValue().empty()) {
             resv->remove("hostname");
@@ -946,6 +950,24 @@ TEST_F(HostReservationParserTest, dhcp6NullAddress) {
     testInvalidConfig<HostReservationParser6>(config);
 }
 
+// This test verifies that the configuration parser throws an exception
+// when the excluded prefix list is shorter.
+TEST_F(HostReservationParserTest, dhcp6ExcludedTooShort) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"prefixes\": [ \"2001:db8::/48\" ],"
+        "\"excluded-prefixes\": [ ] }";
+    testInvalidConfig<HostReservationParser6>(config);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when the excluded prefix list is too long.
+TEST_F(HostReservationParserTest, dhcp6ExcludedTooLong) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"prefixes\": [ \"2001:db8::/48\" ],"
+        "\"excluded-prefixes\": [ \"\", \"\" ] }";
+    testInvalidConfig<HostReservationParser6>(config);
+}
+
 // This test verifies that the configuration parser throws an exception
 // when invalid prefix length type is specified.
 TEST_F(HostReservationParserTest, dhcp6InvalidPrefixLengthType) {
@@ -954,6 +976,15 @@ TEST_F(HostReservationParserTest, dhcp6InvalidPrefixLengthType) {
     testInvalidConfig<HostReservationParser6>(config);
 }
 
+// This test verifies that the configuration parser throws an exception
+// when invalid excluded prefix length type is specified.
+TEST_F(HostReservationParserTest, dhcp6InvalidExcludedPrefixLengthType) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"prefixes\": [ \"2001:db8::/48\" ],"
+        "\"excluded-prefixes\": [ \"2001:db8:0:1::/abc\" ] }";
+    testInvalidConfig<HostReservationParser6>(config);
+}
+
 // This test verifies that the configuration parser throws an exception
 // when invalid prefix length is specified.
 TEST_F(HostReservationParserTest, dhcp6InvalidPrefixLength) {
@@ -962,6 +993,15 @@ TEST_F(HostReservationParserTest, dhcp6InvalidPrefixLength) {
     testInvalidConfig<HostReservationParser6>(config);
 }
 
+// This test verifies that the configuration parser throws an exception
+// when invalid excluded prefix length is specified.
+TEST_F(HostReservationParserTest, dhcp6InvalidExcludedPrefixLength) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"prefixes\": [ \"2001:db8::/48\" ],"
+        "\"excluded-prefixes\": [ \"2001:db8::0:0:1::/64\" ] }";
+    testInvalidConfig<HostReservationParser6>(config);
+}
+
 // This test verifies that the configuration parser throws an exception
 // when empty prefix is specified.
 TEST_F(HostReservationParserTest, dhcp6NullPrefix) {
@@ -970,6 +1010,15 @@ TEST_F(HostReservationParserTest, dhcp6NullPrefix) {
     testInvalidConfig<HostReservationParser6>(config);
 }
 
+// This test verifies that the configuration parser throws an exception
+// when empty excluded prefix is specified.
+TEST_F(HostReservationParserTest, dhcp6NullExcludedPrefix) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"prefixes\": [ \"2001:db8::/48\" ],"
+        "\"excluded-prefixes\": [ \"/64\" ] }";
+    testInvalidConfig<HostReservationParser6>(config);
+}
+
 // This test verifies that the configuration parser throws an exception
 // when only slash is specified for the prefix..
 TEST_F(HostReservationParserTest, dhcp6NullPrefix2) {
@@ -978,6 +1027,15 @@ TEST_F(HostReservationParserTest, dhcp6NullPrefix2) {
     testInvalidConfig<HostReservationParser6>(config);
 }
 
+// This test verifies that the configuration parser throws an exception
+// when only slash is specified for the excluded prefix..
+TEST_F(HostReservationParserTest, dhcp6NullExcludedPrefix2) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"prefixes\": [ \"2001:db8::/48\" ],"
+        "\"excluded-prefixes\": [ \"/\" ] }";
+    testInvalidConfig<HostReservationParser6>(config);
+}
+
 // This test verifies that the configuration parser throws an exception
 // when slash is missing for the prefix..
 TEST_F(HostReservationParserTest, dhcp6NullPrefix3) {
@@ -986,6 +1044,15 @@ TEST_F(HostReservationParserTest, dhcp6NullPrefix3) {
     testInvalidConfig<HostReservationParser6>(config);
 }
 
+// This test verifies that the configuration parser throws an exception
+// when slash is missing for the excluded prefix..
+TEST_F(HostReservationParserTest, dhcp6NullExcludedPrefix3) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"prefixes\": [ \"2001:db8::/48\" ],"
+        "\"excluded-prefixes\": [ \"2001:db8:0:1::\" ] }";
+    testInvalidConfig<HostReservationParser6>(config);
+}
+
 // This test verifies that the configuration parser throws an exception
 // when slash is followed by nothing for the prefix..
 TEST_F(HostReservationParserTest, dhcp6NullPrefix4) {
@@ -994,6 +1061,15 @@ TEST_F(HostReservationParserTest, dhcp6NullPrefix4) {
     testInvalidConfig<HostReservationParser6>(config);
 }
 
+// This test verifies that the configuration parser throws an exception
+// when slash is followed by nothing for the excluded prefix..
+TEST_F(HostReservationParserTest, dhcp6NullExcludedPrefix4) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"prefixes\": [ \"2001:db8::/48\" ],"
+        "\"excluded-prefixes\": [ \"2001:db8:0:1::/\" ] }";
+    testInvalidConfig<HostReservationParser6>(config);
+}
+
 // This test verifies that the configuration parser throws an exception
 // when slash is not followed by a number for the prefix..
 TEST_F(HostReservationParserTest, dhcp6NullPrefix5) {
@@ -1002,6 +1078,15 @@ TEST_F(HostReservationParserTest, dhcp6NullPrefix5) {
     testInvalidConfig<HostReservationParser6>(config);
 }
 
+// This test verifies that the configuration parser throws an exception
+// when slash is not followed by a number for the excluded prefix..
+TEST_F(HostReservationParserTest, dhcp6NullExcludedPrefix5) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"prefixes\": [ \"2001:db8::/48\" ],"
+        "\"excluded-prefixes\": [ \"2001:db8:0:1::/foo\" ] }";
+    testInvalidConfig<HostReservationParser6>(config);
+}
+
 // This test verifies that the configuration parser throws an exception
 // when the same address is reserved twice.
 TEST_F(HostReservationParserTest, dhcp6DuplicatedAddress) {
@@ -1018,6 +1103,33 @@ TEST_F(HostReservationParserTest, dhcp6DuplicatedPrefix) {
     testInvalidConfig<HostReservationParser6>(config);
 }
 
+// This test verifies that the configuration parser throws an exception
+// when the same prefix is reserved twice with different exclude prefixes.
+TEST_F(HostReservationParserTest, dhcp6DuplicatedPrefix2) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"prefixes\": [ \"2001:db8::/48\", \"2001:db8::/48\" ],"
+        "\"excluded-prefixes\": [ \"\", \"2001:db8:0:1::/64\" ] }";
+    testInvalidConfig<HostReservationParser6>(config);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when the excluded prefix is invalid (not in prefix).
+TEST_F(HostReservationParserTest, dhcp6InvalidExcludedPrefix) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"prefixes\": [ \"2001:db8::/48\" ],"
+        "\"excluded-prefixes\": [ \"2001:db8:1::/64\" ] }";
+    testInvalidConfig<HostReservationParser6>(config);
+}
+
+// This test verifies that the configuration parser throws an exception
+// when the excluded prefix is invalid (same length).
+TEST_F(HostReservationParserTest, dhcp6InvalidExcludedPrefix2) {
+    std::string config = "{ \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "\"prefixes\": [ \"2001:db8::/48\" ],"
+        "\"excluded-prefixes\": [ \"2001:db8::/48\" ] }";
+    testInvalidConfig<HostReservationParser6>(config);
+}
+
 // This test verifies that the configuration parser for host reservations
 // throws an exception when unsupported parameter is specified.
 TEST_F(HostReservationParserTest, dhcp6invalidParameterName) {
index e6a2dc642b01f80035182cceaf00f849b3d92dbe..78504cfb8b7581f7eda80e72fda80c139cdecaf9 100644 (file)
@@ -120,6 +120,10 @@ CfgHostsSubnet::toElement() const {
         if (prefixes && prefixes->empty()) {
             resv->remove("prefixes");
         }
+        ConstElementPtr excluded_prefixes = resv->get("excluded-prefixes");
+        if (excluded_prefixes && excluded_prefixes->empty()) {
+            resv->remove("excluded-prefixes");
+        }
         ConstElementPtr hostname = resv->get("hostname");
         if (hostname && hostname->stringValue().empty()) {
             resv->remove("hostname");
@@ -346,6 +350,94 @@ TEST_F(HostReservationsListParserTest, ipv6Reservations) {
     runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
 }
 
+// This test verifies that the parser for the list of the host reservations
+// parses IPv6 reservations with Prefix Exclude options correctly.
+TEST_F(HostReservationsListParserTest, prefixExclude) {
+    // hexadecimal in lower case for toElement()
+    std::string config =
+        "[ "
+        "  { \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
+        "    \"ip-addresses\": [ ],"
+        "    \"prefixes\": [ \"2001:db8::/48\" ],"
+        "    \"excluded-prefixes\": [ \"2001:db8:0:1::/64\" ],"
+        "    \"hostname\": \"foo.example.com\" "
+        "  }, "
+        "  { \"hw-address\": \"01:02:03:04:05:06\","
+        "    \"ip-addresses\": [ \"2001:db8:1::123\" ],"
+        "    \"hostname\": \"bar.example.com\" "
+        "  } "
+        "]";
+
+    ElementPtr config_element = Element::fromJSON(config);
+
+    // Parse configuration.
+    HostCollection hosts;
+    HostReservationsListParser<HostReservationParser6> parser;
+    ASSERT_NO_THROW(parser.parse(SubnetID(2), config_element, hosts));
+
+    for (auto const& h : hosts) {
+        CfgMgr::instance().getStagingCfg()->getCfgHosts()->add(h);
+    }
+
+    CfgHostsPtr cfg_hosts = CfgMgr::instance().getStagingCfg()->getCfgHosts();
+
+    // Get the reservation for the host identified by the HW address.
+    ASSERT_NO_THROW(hosts = cfg_hosts->getAll(Host::IDENT_HWADDR,
+                                              &hwaddr_->hwaddr_[0],
+                                              hwaddr_->hwaddr_.size()));
+    ASSERT_EQ(1, hosts.size());
+
+    // Make sure it belongs to a valid subnet.
+    EXPECT_EQ(SUBNET_ID_UNUSED, 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(Host::IDENT_DUID,
+                                              &duid_->getDuid()[0],
+                                              duid_->getDuid().size()));
+    ASSERT_EQ(1, hosts.size());
+
+    EXPECT_EQ(SUBNET_ID_UNUSED, 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::", prefixes.first->second.getPrefix().toText());
+    EXPECT_EQ(48, prefixes.first->second.getPrefixLen());
+    EXPECT_TRUE(prefixes.first->second.getPDExclude());
+    EXPECT_EQ("2001:db8:0:1::/64", prefixes.first->second.PDExcludetoText());
+
+    // Get back the config from cfg_hosts
+    ElementPtr resv = config_element->getNonConst(0);
+    resv->remove("ip-addresses");
+    config = prettyPrint(config_element);
+    boost::algorithm::to_lower(config);
+    CfgMgr::instance().setFamily(AF_INET6);
+    CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(2));
+    runToElementTest<CfgHostsSubnet>(config, cfg_subnet);
+
+    CfgMgr::instance().setFamily(AF_INET);
+    CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(2));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);
+
+    CfgMgr::instance().setFamily(AF_INET6);
+    CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
+}
+
 // This test verifies that an attempt to add two reservations with the
 // same identifier value will return an error.
 TEST_F(HostReservationsListParserTest, duplicatedIdentifierValue6) {