Browse Source

[fdunparse2] Snapshot for 4o6-subnet fix

Francis Dupont 8 years ago
parent
commit
c186258594

+ 1 - 0
src/bin/dhcp4/tests/Makefile.am

@@ -95,6 +95,7 @@ dhcp4_unittests_SOURCES += decline_unittest.cc
 dhcp4_unittests_SOURCES += kea_controller_unittest.cc
 dhcp4_unittests_SOURCES += dhcp4to6_ipc_unittest.cc
 dhcp4_unittests_SOURCES += simple_parser4_unittest.cc
+dhcp4_unittests_SOURCES += get_config_unittest.cc get_config_unittest.h
 
 nodist_dhcp4_unittests_SOURCES = marker_file.h test_libraries.h
 

+ 63 - 5
src/bin/dhcp4/tests/config_parser_unittest.cc

@@ -31,6 +31,7 @@
 #include "test_libraries.h"
 #include "test_data_files_config.h"
 #include "dhcp4_test_utils.h"
+#include "get_config_unittest.h"
 
 #include <boost/foreach.hpp>
 #include <boost/scoped_ptr.hpp>
@@ -628,12 +629,15 @@ TEST_F(Dhcp4ParserTest, bogusCommand) {
 /// pool definition.
 TEST_F(Dhcp4ParserTest, emptySubnet) {
 
+    std::string config = "{ " + genIfaceConfig() + "," +
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet4\": [  ], "
+        "\"valid-lifetime\": 4000 }";
+
     ConstElementPtr json;
-    EXPECT_NO_THROW(json = parseDHCP4("{ " + genIfaceConfig() + "," +
-                                      "\"rebind-timer\": 2000, "
-                                      "\"renew-timer\": 1000, "
-                                      "\"subnet4\": [  ], "
-                                      "\"valid-lifetime\": 4000 }"));
+    EXPECT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
@@ -655,6 +659,7 @@ TEST_F(Dhcp4ParserTest, unspecifiedRenewTimer) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
@@ -689,6 +694,7 @@ TEST_F(Dhcp4ParserTest, unspecifiedRebindTimer) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
@@ -723,6 +729,7 @@ TEST_F(Dhcp4ParserTest, subnetGlobalDefaults) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
@@ -774,6 +781,7 @@ TEST_F(Dhcp4ParserTest, multipleSubnets) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     int cnt = 0; // Number of reconfigurations
 
@@ -832,6 +840,7 @@ TEST_F(Dhcp4ParserTest, multipleSubnetsExplicitIDs) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     int cnt = 0; // Number of reconfigurations
     do {
@@ -1040,6 +1049,7 @@ TEST_F(Dhcp4ParserTest, nextServerGlobal) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
@@ -1070,6 +1080,7 @@ TEST_F(Dhcp4ParserTest, nextServerSubnet) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
@@ -1168,6 +1179,7 @@ TEST_F(Dhcp4ParserTest, nextServerOverride) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
@@ -1206,8 +1218,10 @@ TEST_F(Dhcp4ParserTest, echoClientId) {
 
     ConstElementPtr json_false;
     ASSERT_NO_THROW(json_false = parseDHCP4(config_false));
+    extractConfig(config_false);
     ConstElementPtr json_true;
     ASSERT_NO_THROW(json_true = parseDHCP4(config_true));
+    extractConfig(config_true);
 
     // Let's check the default. It should be true
     ASSERT_TRUE(CfgMgr::instance().getStagingCfg()->getEchoClientId());
@@ -1248,6 +1262,7 @@ TEST_F(Dhcp4ParserTest, matchClientIdNoGlobal) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     ASSERT_NO_THROW(status = configureDhcp4Server(*srv_, json));
@@ -1285,6 +1300,7 @@ TEST_F(Dhcp4ParserTest, matchClientIdGlobal) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     ASSERT_NO_THROW(status = configureDhcp4Server(*srv_, json));
@@ -1317,6 +1333,7 @@ TEST_F(Dhcp4ParserTest, subnetLocal) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
@@ -1357,6 +1374,7 @@ TEST_F(Dhcp4ParserTest, multiplePools) {
         "\"valid-lifetime\": 4000 }";
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     ASSERT_NO_THROW(status = configureDhcp4Server(*srv_, json));
@@ -1429,6 +1447,7 @@ TEST_F(Dhcp4ParserTest, poolPrefixLen) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
@@ -1585,6 +1604,7 @@ TEST_F(Dhcp4ParserTest, optionDefIpv4Address) {
         "}";
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseOPTION_DEF(config, true));
+    extractConfig(config);
 
     // Make sure that the particular option definition does not exist.
     OptionDefinitionPtr def = CfgMgr::instance().getStagingCfg()->
@@ -1651,6 +1671,7 @@ TEST_F(Dhcp4ParserTest, optionDefRecord) {
         "}";
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseOPTION_DEF(config));
+    extractConfig(config);
 
     // Make sure that the particular option definition does not exist.
     OptionDefinitionPtr def = CfgMgr::instance().getStagingCfg()->
@@ -1705,6 +1726,7 @@ TEST_F(Dhcp4ParserTest, optionDefMultiple) {
         "}";
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseOPTION_DEF(config));
+    extractConfig(config);
 
     // Make sure that the option definitions do not exist yet.
     ASSERT_FALSE(CfgMgr::instance().getStagingCfg()->
@@ -1813,6 +1835,7 @@ TEST_F(Dhcp4ParserTest, optionDefArray) {
         "}";
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseOPTION_DEF(config));
+    extractConfig(config);
 
     // Make sure that the particular option definition does not exist.
     OptionDefinitionPtr def = CfgMgr::instance().getStagingCfg()->
@@ -1855,6 +1878,7 @@ TEST_F(Dhcp4ParserTest, optionDefEncapsulate) {
         "}";
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseOPTION_DEF(config));
+    extractConfig(config);
 
     // Make sure that the particular option definition does not exist.
     OptionDefinitionPtr def = CfgMgr::instance().getStagingCfg()->
@@ -2056,6 +2080,7 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
         "}";
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseOPTION_DEF(config));
+    extractConfig(config);
 
     OptionDefinitionPtr def = CfgMgr::instance().getStagingCfg()->
         getCfgOptionDef()->get(DHCP4_OPTION_SPACE, 109);
@@ -2112,6 +2137,7 @@ TEST_F(Dhcp4ParserTest, optionStandardDefOverride) {
         "  } ]"
         "}";
     ASSERT_NO_THROW(json = parseOPTION_DEF(config));
+    extractConfig(config);
 
     // Use the configuration string to create new option definition.
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
@@ -2155,6 +2181,7 @@ TEST_F(Dhcp4ParserTest, optionDataDefaultsGlobal) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
     checkResult(x, 0);
@@ -2225,6 +2252,7 @@ TEST_F(Dhcp4ParserTest, optionDataDefaultsSubnet) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
     checkResult(x, 0);
@@ -2306,6 +2334,7 @@ TEST_F(Dhcp4ParserTest, optionDataTwoSpaces) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
@@ -2381,6 +2410,7 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
@@ -2437,6 +2467,7 @@ TEST_F(Dhcp4ParserTest, optionDataEncapsulate) {
         "}";
 
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
     ASSERT_TRUE(status);
@@ -2498,6 +2529,7 @@ TEST_F(Dhcp4ParserTest, optionDataInSingleSubnet) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
     checkResult(x, 0);
@@ -2646,6 +2678,7 @@ TEST_F(Dhcp4ParserTest, optionDataInMultipleSubnets) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
     checkResult(x, 0);
@@ -2920,6 +2953,7 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
@@ -2976,6 +3010,7 @@ TEST_F(Dhcp4ParserTest, stdOptionDataEncapsulate) {
 
 
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
     ASSERT_TRUE(status);
@@ -3055,6 +3090,7 @@ TEST_F(Dhcp4ParserTest, vendorOptionsHex) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
@@ -3110,6 +3146,7 @@ TEST_F(Dhcp4ParserTest, vendorOptionsCsv) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
@@ -3287,6 +3324,7 @@ TEST_F(Dhcp4ParserTest, selectedInterfaces) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     ConstElementPtr status;
 
@@ -3326,6 +3364,7 @@ TEST_F(Dhcp4ParserTest, allInterfaces) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     ConstElementPtr status;
 
@@ -3424,6 +3463,7 @@ TEST_F(Dhcp4ParserTest, d2ClientConfig) {
     // Convert the JSON string to configuration elements.
     ConstElementPtr config;
     ASSERT_NO_THROW(config = parseDHCP4(config_str, true));
+    extractConfig(config_str);
 
     // Pass the configuration in for parsing.
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, config));
@@ -3523,6 +3563,7 @@ TEST_F(Dhcp4ParserTest, subnetRelayInfo) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
 
@@ -3710,6 +3751,7 @@ TEST_F(Dhcp4ParserTest, reservations) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
     checkResult(x, 0);
@@ -3861,6 +3903,7 @@ TEST_F(Dhcp4ParserTest, reservationWithOptionDefinition) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config, true));
+    extractConfig(config);
 
     EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
     checkResult(x, 0);
@@ -4045,6 +4088,7 @@ TEST_F(Dhcp4ParserTest, hostReservationPerSubnet) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(hr_config));
+    extractConfig(hr_config);
     ConstElementPtr result;
     EXPECT_NO_THROW(result = configureDhcp4Server(*srv_, json));
 
@@ -4092,6 +4136,7 @@ TEST_F(Dhcp4ParserTest, declineTimerDefault) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
 
@@ -4113,6 +4158,7 @@ TEST_F(Dhcp4ParserTest, dhcp4o6portDefault) {
         "}";
     ConstElementPtr config;
     ASSERT_NO_THROW(config = parseDHCP4(config_txt));
+    extractConfig(config_txt);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, config));
@@ -4138,6 +4184,7 @@ TEST_F(Dhcp4ParserTest, declineTimer) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
 
@@ -4193,6 +4240,7 @@ TEST_F(Dhcp4ParserTest, expiredLeasesProcessing) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
@@ -4263,6 +4311,7 @@ TEST_F(Dhcp4ParserTest, 4o6default) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
 
@@ -4297,6 +4346,7 @@ TEST_F(Dhcp4ParserTest, 4o6subnet) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
 
@@ -4393,6 +4443,7 @@ TEST_F(Dhcp4ParserTest, 4o6iface) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
 
@@ -4429,6 +4480,7 @@ TEST_F(Dhcp4ParserTest, 4o6subnetIface) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
 
@@ -4467,6 +4519,7 @@ TEST_F(Dhcp4ParserTest, 4o6subnetInterfaceId) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
 
@@ -4517,6 +4570,7 @@ TEST_F(Dhcp4ParserTest, validClientClassDictionary) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP4(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
@@ -4563,6 +4617,7 @@ TEST_F(Dhcp4ParserTest, invalidClientClassDictionary) {
 // Test verifies that regular configuration does not provide any user context
 // in the address pool.
 TEST_F(Dhcp4ParserTest, poolUserContextMissing) {
+    extractConfig(PARSER_CONFIGS[0]);
     PoolPtr pool;
     getPool(string(PARSER_CONFIGS[0]), 0, 0, pool);
     ASSERT_TRUE(pool);
@@ -4572,6 +4627,7 @@ TEST_F(Dhcp4ParserTest, poolUserContextMissing) {
 // Test verifies that it's possible to specify empty user context in the
 // address pool.
 TEST_F(Dhcp4ParserTest, poolUserContextEmpty) {
+    extractConfig(PARSER_CONFIGS[1]);
     PoolPtr pool;
     getPool(string(PARSER_CONFIGS[1]), 0, 0, pool);
     ASSERT_TRUE(pool);
@@ -4586,6 +4642,7 @@ TEST_F(Dhcp4ParserTest, poolUserContextEmpty) {
 // Test verifies that it's possible to specify parameters in the user context
 // in the address pool.
 TEST_F(Dhcp4ParserTest, poolUserContextData) {
+    extractConfig(PARSER_CONFIGS[2]);
     PoolPtr pool;
     getPool(string(PARSER_CONFIGS[2]), 0, 0, pool);
     ASSERT_TRUE(pool);
@@ -4619,6 +4676,7 @@ TEST_F(Dhcp4ParserTest, poolUserContextData) {
 // Test verifies that it's possible to specify parameters in the user context
 // in the min-max address pool.
 TEST_F(Dhcp4ParserTest, pooMinMaxlUserContext) {
+    extractConfig(PARSER_CONFIGS[3]);
     PoolPtr pool;
     getPool(string(PARSER_CONFIGS[3]), 0, 0, pool);
     ASSERT_TRUE(pool);

+ 1 - 0
src/bin/dhcp4/tests/dhcp4_test_utils.cc

@@ -70,6 +70,7 @@ Dhcpv4SrvTest::Dhcpv4SrvTest()
     subnet_->getCfgOption()->add(opt_routers, false, DHCP4_OPTION_SPACE);
 
     CfgMgr::instance().clear();
+    CfgMgr::instance().setFamily(AF_INET);
     CfgMgr::instance().getStagingCfg()->getCfgSubnets4()->add(subnet_);
     CfgMgr::instance().commit();
 

+ 345 - 0
src/bin/dhcp4/tests/get_config_unittest.cc

@@ -0,0 +1,345 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <cc/command_interpreter.h>
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <cc/cfg_to_element.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp4/tests/get_config_unittest.h>
+#include <dhcp4/dhcp4_srv.h>
+#include <dhcp4/json_config_parser.h>
+#include <dhcp4/simple_parser4.h>
+
+#include <boost/algorithm/string.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <string>
+#include <sstream>
+#include <list>
+
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @name How to fill configurations
+///
+/// Copy get_config_unittest.cc.skel into get_config_unittest.cc
+///
+/// For the extracted configurations define the EXTRACT_CONFIG and
+/// recompile this file. Run dhcp4_unittests on Dhcp4ParserTest
+/// redirecting the standard error to a temporary file, e.g. by
+/// @code
+///    ./dhcp4_unittests --gtest_filter="Dhcp4Parser*" > /dev/null 2> x
+/// @endcode
+///
+/// Update EXTRACTED_CONFIGS with the file content
+///
+/// When configurations have been extracted the corresponding unparsed
+/// configurations must be generated. To do that define GENERATE_ACTION
+/// and recompile this file. Run dhcp4_unittests on Dhcp4GetConfigTest
+/// redirecting the standard error to a temporary file, e.g. by
+/// @code
+///    ./dhcp4_unittests --gtest_filter="Dhcp4GetConfig*" > /dev/null 2> u
+/// @endcode
+///
+/// Update UNPARSED_CONFIGS with the file content, recompile this file
+/// without EXTRACT_CONFIG and GENERATE_ACTION.
+///
+/// @note Check for failures at each step!
+/// @note The tests of this file do not check if configs returned
+/// by @ref isc::dhcp::CfgToElement::ToElement() are complete.
+/// This has to be done manually.
+///
+///@{
+/// @brief extracted configurations
+const char* EXTRACTED_CONFIGS[] = {
+    // "to be replaced"
+};
+
+/// @brief unparsed configurations
+const char* UNPARSED_CONFIGS[] = {
+    // "to be replaced"
+};
+
+/// @brief the number of configurations
+const size_t max_config_counter = sizeof(EXTRACTED_CONFIGS) / sizeof(char*);
+///@}
+
+/// @brief the extraction counter
+///
+/// < 0 means do not extract, >= 0 means extract on extractConfig() calls
+/// and increment
+#ifdef EXTRACT_CONFIG
+int extract_count = 0;
+#else
+int extract_count = -1;
+#endif
+
+/// @brief the generate action
+/// false means do nothing, true means unparse extracted configurations
+#ifdef GENERATE_ACTION
+const bool generate_action = true;
+#else
+const bool generate_action = false;
+static_assert(max_config_counter == sizeof(UNPARSED_CONFIGS) / sizeof(char*),
+              "unparsed configurations must be generated");
+#endif
+
+/// @brief format and output a configuration
+void
+outputFormatted(const std::string& config) {
+    // pretty print it
+    ConstElementPtr json = parseJSON(config);
+    std::string prettier = prettyPrint(json, 4, 4);
+    // get it as a line array
+    std::list<std::string> lines;
+    boost::split(lines, prettier, boost::is_any_of("\n"));
+    // add escapes using again JSON
+    std::list<std::string> escapes;
+    while (!lines.empty()) {
+        const std::string& line = lines.front();
+        ConstElementPtr escaping = Element::create(line + "\n");
+        escapes.push_back(escaping->str());
+        lines.pop_front();
+    }
+    // output them on std::cerr
+    while (!escapes.empty()) {
+        std::cerr << "\n" << escapes.front();
+        escapes.pop_front();
+    }
+}
+
+};
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @ref isc::dhcp::test::extractConfig in the header
+void
+extractConfig(const std::string& config) {
+    // skip when disable
+    if (extract_count < 0) {
+        return;
+    }
+    // mark beginning
+    if (extract_count == 0) {
+        // header (note there is no trailer)
+        std::cerr << "/// put this after const char* EXTRACTED_CONFIGS[] = {\n";
+    } else {
+        // end of previous configuration
+        std::cerr << ",\n";
+    }
+    std::cerr << "    // CONFIGURATION " << extract_count;
+    try {
+        outputFormatted(config);
+    } catch (...) {
+        // mark error
+        std::cerr << "\n//// got an error\n";
+    }
+    ++extract_count;
+}
+
+};
+};
+};
+
+namespace {
+
+/// Test fixture class (code from Dhcp4ParserTest)
+class Dhcp4GetConfigTest : public ::testing::TestWithParam<size_t> {
+public:
+    Dhcp4GetConfigTest()
+    : rcode_(-1) {
+        // Open port 0 means to not do anything at all. We don't want to
+        // deal with sockets here, just check if configuration handling
+        // is sane.
+        srv_.reset(new Dhcpv4Srv(0));
+        // Create fresh context.
+        resetConfiguration();
+    }
+
+    ~Dhcp4GetConfigTest() {
+        resetConfiguration();
+    };
+
+    /// @brief Parse and Execute configuration
+    ///
+    /// Parses a configuration and executes a configuration of the server.
+    /// If the operation fails, the current test will register a failure.
+    ///
+    /// @param config Configuration to parse
+    /// @param operation Operation being performed.  In the case of an error,
+    ///        the error text will include the string "unable to <operation>.".
+    ///
+    /// @return true if the configuration succeeded, false if not.
+    bool
+    executeConfiguration(const std::string& config, const char* operation) {
+        // clear config manager
+        CfgMgr::instance().clear();
+
+        // enable fake network interfaces
+        IfaceMgrTestConfig test_config(true);
+
+        // try JSON parser
+        ConstElementPtr json;
+        try {
+            json = parseJSON(config);
+        } catch (const std::exception& ex) {
+            ADD_FAILURE() << "invalid JSON for " << operation
+                          << " failed with " << ex.what()
+                          << " on\n" << config << "\n";
+            return (false);
+        }
+
+        // try DHCP4 parser
+        try {
+            json = parseDHCP4(config, true);
+        } catch (...) {
+            ADD_FAILURE() << "parsing failed for " << operation
+                          << " on\n" << prettyPrint(json) << "\n";
+            return (false);
+        }
+
+        // try DHCP4 configure
+        ConstElementPtr status;
+        try {
+            status = configureDhcp4Server(*srv_, json);
+        } catch (const std::exception& ex) {
+            ADD_FAILURE() << "configure for " << operation
+                          << " failed with " << ex.what()
+                          << " on\n" << prettyPrint(json) << "\n";
+            return (false);
+        }
+
+        // The status object must not be NULL
+        if (!status) {
+            ADD_FAILURE() << "configure for " << operation
+                          << " returned null on\n"
+                          << prettyPrint(json) << "\n";
+            return (false);
+        }
+
+        // Returned value should be 0 (configuration success)
+        comment_ = parseAnswer(rcode_, status);
+        if (rcode_ != 0) {
+            string reason = "";
+            if (comment_) {
+                reason = string(" (") + comment_->stringValue() + string(")");
+            }
+            ADD_FAILURE() << "configure for " << operation
+                          << " returned error code "
+                          << rcode_ << reason << " on\n"
+                          << prettyPrint(json) << "\n";
+            return (false);
+        }
+        return (true);
+    }
+
+    /// @brief Reset configuration database.
+    ///
+    /// This function resets configuration data base by
+    /// removing all subnets and option-data. Reset must
+    /// be performed after each test to make sure that
+    /// contents of the database do not affect result of
+    /// subsequent tests.
+    void resetConfiguration() {
+        string config = "{"
+            "\"interfaces-config\": { \"interfaces\": [ \"*\" ] },"
+            "\"valid-lifetime\": 4000, "
+            "\"subnet4\": [ ], "
+            "\"dhcp-ddns\": { \"enable-updates\" : false }, "
+            "\"option-def\": [ ], "
+            "\"option-data\": [ ] }";
+        EXPECT_TRUE(executeConfiguration(config, "reset configuration"));
+        CfgMgr::instance().clear();
+        CfgMgr::instance().setFamily(AF_INET);
+    }
+
+    boost::scoped_ptr<Dhcpv4Srv> srv_;  ///< DHCP4 server under test
+    int rcode_;                         ///< Return code from element parsing
+    ConstElementPtr comment_;           ///< Reason for parse fail
+};
+
+/// Test a configuration
+TEST_P(Dhcp4GetConfigTest, run) {
+    // configurations have not been extracted yet
+    if (max_config_counter == 0) {
+        return;
+    }
+
+    // get the index of configurations to test
+    size_t config_counter = GetParam();
+
+    // emit unparsed header if wanted
+    if ((config_counter == 0) && generate_action) {
+        std::cerr << "///put this after const char* UNPARSED_CONFIGS[] = {\n";
+    }
+
+    // get the extracted configuration
+    std::string config = EXTRACTED_CONFIGS[config_counter];
+    std::ostringstream ss;
+    ss << "extracted config #" << config_counter;
+
+    // execute the extracted configuration
+    ASSERT_TRUE(executeConfiguration(config, ss.str().c_str()));
+
+    // unparse it
+    ConstSrvConfigPtr extracted = CfgMgr::instance().getStagingCfg();
+    ConstElementPtr unparsed;
+    ASSERT_NO_THROW(unparsed = extracted->toElement());
+    ConstElementPtr dhcp;
+    ASSERT_NO_THROW(dhcp = unparsed->get("Dhcp4"));
+    ASSERT_TRUE(dhcp);
+
+    // dump if wanted else check
+    std::string expected;
+    if (generate_action) {
+        if (config_counter > 0) {
+            std::cerr << ",\n";
+        }
+        std::cerr << "    // CONFIGURATION " << config_counter;
+        ASSERT_NO_THROW(expected = prettyPrint(dhcp));
+        ASSERT_NO_THROW(outputFormatted(dhcp->str()));
+    } else {
+        expected = UNPARSED_CONFIGS[config_counter];
+        ConstElementPtr json;
+        ASSERT_NO_THROW(json = parseDHCP4(expected, true));
+        EXPECT_TRUE(isEquivalent(dhcp, json));
+        std::string current = prettyPrint(dhcp, 4, 4) + "\n";
+        EXPECT_EQ(expected, current);
+        if (expected != current) {
+            expected = current;
+        }
+    }
+
+    // execute the dhcp configuration
+    ss.str("");
+    ss << "unparsed config #" << config_counter;
+    EXPECT_TRUE(executeConfiguration(expected, ss.str().c_str()));
+
+    // is it a fixed point?
+    ConstSrvConfigPtr extracted2 = CfgMgr::instance().getStagingCfg();
+    ConstElementPtr unparsed2;
+    ASSERT_NO_THROW(unparsed2 = extracted2->toElement());
+    ASSERT_TRUE(unparsed2);
+    EXPECT_TRUE(isEquivalent(unparsed, unparsed2));
+}
+
+/// Define the parametrized test loop
+INSTANTIATE_TEST_CASE_P(Dhcp4GetConfigTest, Dhcp4GetConfigTest,
+                        ::testing::Range(0UL, max_config_counter));
+
+};

+ 345 - 0
src/bin/dhcp4/tests/get_config_unittest.cc.skel

@@ -0,0 +1,345 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <cc/command_interpreter.h>
+#include <cc/data.h>
+#include <cc/simple_parser.h>
+#include <cc/cfg_to_element.h>
+#include <dhcp/tests/iface_mgr_test_config.h>
+#include <dhcpsrv/cfgmgr.h>
+#include <dhcp4/tests/dhcp4_test_utils.h>
+#include <dhcp4/tests/get_config_unittest.h>
+#include <dhcp4/dhcp4_srv.h>
+#include <dhcp4/json_config_parser.h>
+#include <dhcp4/simple_parser4.h>
+
+#include <boost/algorithm/string.hpp>
+#include <gtest/gtest.h>
+
+#include <iostream>
+#include <string>
+#include <sstream>
+#include <list>
+
+using namespace isc::config;
+using namespace isc::data;
+using namespace isc::dhcp;
+using namespace isc::dhcp::test;
+
+namespace {
+
+/// @name How to fill configurations
+///
+/// Copy get_config_unittest.cc.skel into get_config_unittest.cc
+///
+/// For the extracted configurations define the EXTRACT_CONFIG and
+/// recompile this file. Run dhcp4_unittests on Dhcp4ParserTest
+/// redirecting the standard error to a temporary file, e.g. by
+/// @code
+///    ./dhcp4_unittests --gtest_filter="Dhcp4Parser*" > /dev/null 2> x
+/// @endcode
+///
+/// Update EXTRACTED_CONFIGS with the file content
+///
+/// When configurations have been extracted the corresponding unparsed
+/// configurations must be generated. To do that define GENERATE_ACTION
+/// and recompile this file. Run dhcp4_unittests on Dhcp4GetConfigTest
+/// redirecting the standard error to a temporary file, e.g. by
+/// @code
+///    ./dhcp4_unittests --gtest_filter="Dhcp4GetConfig*" > /dev/null 2> u
+/// @endcode
+///
+/// Update UNPARSED_CONFIGS with the file content, recompile this file
+/// without EXTRACT_CONFIG and GENERATE_ACTION.
+///
+/// @note Check for failures at each step!
+/// @note The tests of this file do not check if configs returned
+/// by @ref isc::dhcp::CfgToElement::ToElement() are complete.
+/// This has to be done manually.
+///
+///@{
+/// @brief extracted configurations
+const char* EXTRACTED_CONFIGS[] = {
+    // "to be replaced"
+};
+
+/// @brief unparsed configurations
+const char* UNPARSED_CONFIGS[] = {
+    // "to be replaced"
+};
+
+/// @brief the number of configurations
+const size_t max_config_counter = sizeof(EXTRACTED_CONFIGS) / sizeof(char*);
+///@}
+
+/// @brief the extraction counter
+///
+/// < 0 means do not extract, >= 0 means extract on extractConfig() calls
+/// and increment
+#ifdef EXTRACT_CONFIG
+int extract_count = 0;
+#else
+int extract_count = -1;
+#endif
+
+/// @brief the generate action
+/// false means do nothing, true means unparse extracted configurations
+#ifdef GENERATE_ACTION
+const bool generate_action = true;
+#else
+const bool generate_action = false;
+static_assert(max_config_counter == sizeof(UNPARSED_CONFIGS) / sizeof(char*),
+              "unparsed configurations must be generated");
+#endif
+
+/// @brief format and output a configuration
+void
+outputFormatted(const std::string& config) {
+    // pretty print it
+    ConstElementPtr json = parseJSON(config);
+    std::string prettier = prettyPrint(json, 4, 4);
+    // get it as a line array
+    std::list<std::string> lines;
+    boost::split(lines, prettier, boost::is_any_of("\n"));
+    // add escapes using again JSON
+    std::list<std::string> escapes;
+    while (!lines.empty()) {
+        const std::string& line = lines.front();
+        ConstElementPtr escaping = Element::create(line + "\n");
+        escapes.push_back(escaping->str());
+        lines.pop_front();
+    }
+    // output them on std::cerr
+    while (!escapes.empty()) {
+        std::cerr << "\n" << escapes.front();
+        escapes.pop_front();
+    }
+}
+
+};
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @ref isc::dhcp::test::extractConfig in the header
+void
+extractConfig(const std::string& config) {
+    // skip when disable
+    if (extract_count < 0) {
+        return;
+    }
+    // mark beginning
+    if (extract_count == 0) {
+        // header (note there is no trailer)
+        std::cerr << "/// put this after const char* EXTRACTED_CONFIGS[] = {\n";
+    } else {
+        // end of previous configuration
+        std::cerr << ",\n";
+    }
+    std::cerr << "    // CONFIGURATION " << extract_count;
+    try {
+        outputFormatted(config);
+    } catch (...) {
+        // mark error
+        std::cerr << "\n//// got an error\n";
+    }
+    ++extract_count;
+}
+
+};
+};
+};
+
+namespace {
+
+/// Test fixture class (code from Dhcp4ParserTest)
+class Dhcp4GetConfigTest : public ::testing::TestWithParam<size_t> {
+public:
+    Dhcp4GetConfigTest()
+    : rcode_(-1) {
+        // Open port 0 means to not do anything at all. We don't want to
+        // deal with sockets here, just check if configuration handling
+        // is sane.
+        srv_.reset(new Dhcpv4Srv(0));
+        // Create fresh context.
+        resetConfiguration();
+    }
+
+    ~Dhcp4GetConfigTest() {
+        resetConfiguration();
+    };
+
+    /// @brief Parse and Execute configuration
+    ///
+    /// Parses a configuration and executes a configuration of the server.
+    /// If the operation fails, the current test will register a failure.
+    ///
+    /// @param config Configuration to parse
+    /// @param operation Operation being performed.  In the case of an error,
+    ///        the error text will include the string "unable to <operation>.".
+    ///
+    /// @return true if the configuration succeeded, false if not.
+    bool
+    executeConfiguration(const std::string& config, const char* operation) {
+        // clear config manager
+        CfgMgr::instance().clear();
+
+        // enable fake network interfaces
+        IfaceMgrTestConfig test_config(true);
+
+        // try JSON parser
+        ConstElementPtr json;
+        try {
+            json = parseJSON(config);
+        } catch (const std::exception& ex) {
+            ADD_FAILURE() << "invalid JSON for " << operation
+                          << " failed with " << ex.what()
+                          << " on\n" << config << "\n";
+            return (false);
+        }
+
+        // try DHCP4 parser
+        try {
+            json = parseDHCP4(config, true);
+        } catch (...) {
+            ADD_FAILURE() << "parsing failed for " << operation
+                          << " on\n" << prettyPrint(json) << "\n";
+            return (false);
+        }
+
+        // try DHCP4 configure
+        ConstElementPtr status;
+        try {
+            status = configureDhcp4Server(*srv_, json);
+        } catch (const std::exception& ex) {
+            ADD_FAILURE() << "configure for " << operation
+                          << " failed with " << ex.what()
+                          << " on\n" << prettyPrint(json) << "\n";
+            return (false);
+        }
+
+        // The status object must not be NULL
+        if (!status) {
+            ADD_FAILURE() << "configure for " << operation
+                          << " returned null on\n"
+                          << prettyPrint(json) << "\n";
+            return (false);
+        }
+
+        // Returned value should be 0 (configuration success)
+        comment_ = parseAnswer(rcode_, status);
+        if (rcode_ != 0) {
+            string reason = "";
+            if (comment_) {
+                reason = string(" (") + comment_->stringValue() + string(")");
+            }
+            ADD_FAILURE() << "configure for " << operation
+                          << " returned error code "
+                          << rcode_ << reason << " on\n"
+                          << prettyPrint(json) << "\n";
+            return (false);
+        }
+        return (true);
+    }
+
+    /// @brief Reset configuration database.
+    ///
+    /// This function resets configuration data base by
+    /// removing all subnets and option-data. Reset must
+    /// be performed after each test to make sure that
+    /// contents of the database do not affect result of
+    /// subsequent tests.
+    void resetConfiguration() {
+        string config = "{"
+            "\"interfaces-config\": { \"interfaces\": [ \"*\" ] },"
+            "\"valid-lifetime\": 4000, "
+            "\"subnet4\": [ ], "
+            "\"dhcp-ddns\": { \"enable-updates\" : false }, "
+            "\"option-def\": [ ], "
+            "\"option-data\": [ ] }";
+        EXPECT_TRUE(executeConfiguration(config, "reset configuration"));
+        CfgMgr::instance().clear();
+        CfgMgr::instance().setFamily(AF_INET);
+    }
+
+    boost::scoped_ptr<Dhcpv4Srv> srv_;  ///< DHCP4 server under test
+    int rcode_;                         ///< Return code from element parsing
+    ConstElementPtr comment_;           ///< Reason for parse fail
+};
+
+/// Test a configuration
+TEST_P(Dhcp4GetConfigTest, run) {
+    // configurations have not been extracted yet
+    if (max_config_counter == 0) {
+        return;
+    }
+
+    // get the index of configurations to test
+    size_t config_counter = GetParam();
+
+    // emit unparsed header if wanted
+    if ((config_counter == 0) && generate_action) {
+        std::cerr << "///put this after const char* UNPARSED_CONFIGS[] = {\n";
+    }
+
+    // get the extracted configuration
+    std::string config = EXTRACTED_CONFIGS[config_counter];
+    std::ostringstream ss;
+    ss << "extracted config #" << config_counter;
+
+    // execute the extracted configuration
+    ASSERT_TRUE(executeConfiguration(config, ss.str().c_str()));
+
+    // unparse it
+    ConstSrvConfigPtr extracted = CfgMgr::instance().getStagingCfg();
+    ConstElementPtr unparsed;
+    ASSERT_NO_THROW(unparsed = extracted->toElement());
+    ConstElementPtr dhcp;
+    ASSERT_NO_THROW(dhcp = unparsed->get("Dhcp4"));
+    ASSERT_TRUE(dhcp);
+
+    // dump if wanted else check
+    std::string expected;
+    if (generate_action) {
+        if (config_counter > 0) {
+            std::cerr << ",\n";
+        }
+        std::cerr << "    // CONFIGURATION " << config_counter;
+        ASSERT_NO_THROW(expected = prettyPrint(dhcp));
+        ASSERT_NO_THROW(outputFormatted(dhcp->str()));
+    } else {
+        expected = UNPARSED_CONFIGS[config_counter];
+        ConstElementPtr json;
+        ASSERT_NO_THROW(json = parseDHCP4(expected, true));
+        EXPECT_TRUE(isEquivalent(dhcp, json));
+        std::string current = prettyPrint(dhcp, 4, 4) + "\n";
+        EXPECT_EQ(expected, current);
+        if (expected != current) {
+            expected = current;
+        }
+    }
+
+    // execute the dhcp configuration
+    ss.str("");
+    ss << "unparsed config #" << config_counter;
+    EXPECT_TRUE(executeConfiguration(expected, ss.str().c_str()));
+
+    // is it a fixed point?
+    ConstSrvConfigPtr extracted2 = CfgMgr::instance().getStagingCfg();
+    ConstElementPtr unparsed2;
+    ASSERT_NO_THROW(unparsed2 = extracted2->toElement());
+    ASSERT_TRUE(unparsed2);
+    EXPECT_TRUE(isEquivalent(unparsed, unparsed2));
+}
+
+/// Define the parametrized test loop
+INSTANTIATE_TEST_CASE_P(Dhcp4GetConfigTest, Dhcp4GetConfigTest,
+                        ::testing::Range(0UL, max_config_counter));
+
+};

+ 27 - 0
src/bin/dhcp4/tests/get_config_unittest.h

@@ -0,0 +1,27 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef GET_CONFIG_UNITTEST_H
+#define GET_CONFIG_UNITTEST_H
+
+#include <string.h>
+
+namespace isc {
+namespace dhcp {
+namespace test {
+
+/// @brief Extract a configuration as a C++ source for JSON on std::cerr
+///
+/// This function should be called when a configuration in an unit test
+/// is interesting and should be extracted. Do nothing when extract_count
+/// is negative.
+void extractConfig(const std::string& config);
+
+};
+};
+};
+
+#endif // GET_CONFIG_UNITTEST_H

+ 1 - 0
src/bin/dhcp6/tests/Makefile.am

@@ -96,6 +96,7 @@ dhcp6_unittests_SOURCES += dhcp6to4_ipc_unittest.cc
 dhcp6_unittests_SOURCES += classify_unittests.cc
 dhcp6_unittests_SOURCES += parser_unittest.cc
 dhcp6_unittests_SOURCES += simple_parser6_unittest.cc
+dhcp6_unittests_SOURCES += get_config_unittest.cc get_config_unittest.h
 
 nodist_dhcp6_unittests_SOURCES  = marker_file.h test_libraries.h
 

+ 104 - 42
src/bin/dhcp6/tests/config_parser_unittest.cc

@@ -30,6 +30,7 @@
 #include "test_libraries.h"
 #include "marker_file.h"
 #include "dhcp6_test_utils.h"
+#include "get_config_unittest.h"
 
 #include <boost/foreach.hpp>
 #include <gtest/gtest.h>
@@ -777,16 +778,20 @@ TEST_F(Dhcp6ParserTest, bogusCommand) {
 /// subnets defined can be accepted.
 TEST_F(Dhcp6ParserTest, emptySubnet) {
 
+    string config = "{ " + genIfaceConfig() + ","
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [  ], "
+        "\"valid-lifetime\": 4000 }";
+
     ConstElementPtr json;
-    ASSERT_NO_THROW(json = parseDHCP6("{ " + genIfaceConfig() + ","
-                                      "\"preferred-lifetime\": 3000,"
-                                      "\"rebind-timer\": 2000, "
-                                      "\"renew-timer\": 1000, "
-                                      "\"subnet6\": [  ], "
-                                      "\"valid-lifetime\": 4000 }"));
+    ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
+    
 
     // returned value should be 0 (success)
     checkResult(status, 0);
@@ -807,6 +812,7 @@ TEST_F(Dhcp6ParserTest, subnetGlobalDefaults) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
@@ -860,6 +866,7 @@ TEST_F(Dhcp6ParserTest, multipleSubnets) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     do {
         EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
@@ -919,6 +926,7 @@ TEST_F(Dhcp6ParserTest, multipleSubnetsExplicitIDs) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     do {
         EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
@@ -1062,6 +1070,7 @@ TEST_F(Dhcp6ParserTest, reconfigureRemoveSubnet) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config4));
+    extractConfig(config4);
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
     checkResult(x, 0);
 
@@ -1134,6 +1143,7 @@ TEST_F(Dhcp6ParserTest, subnetLocal) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
@@ -1156,19 +1166,22 @@ TEST_F(Dhcp6ParserTest, subnetInterface) {
 
     // There should be at least one interface
 
-    string config = "{ " + genIfaceConfig() + ","
-        "\"preferred-lifetime\": 3000,"
-        "\"rebind-timer\": 2000, "
-        "\"renew-timer\": 1000, "
-        "\"subnet6\": [ { "
-        "    \"pools\": [ { \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
-        "    \"interface\": \"" + valid_iface_ + "\","
-        "    \"subnet\": \"2001:db8:1::/64\" } ],"
-        "\"valid-lifetime\": 4000 }";
-    cout << config << endl;
+    auto config = [this](string iface) {
+        return ("{ " + genIfaceConfig() + ","
+                "\"preferred-lifetime\": 3000,"
+                "\"rebind-timer\": 2000, "
+                "\"renew-timer\": 1000, "
+                "\"subnet6\": [ { "
+                "    \"pools\": [ { "
+                "        \"pool\": \"2001:db8:1::1 - 2001:db8:1::ffff\" } ],"
+                "    \"interface\": \"" + iface + "\","
+                "    \"subnet\": \"2001:db8:1::/64\" } ],"
+                "\"valid-lifetime\": 4000 }"); };
+    cout << config(valid_iface_) << endl;
 
     ConstElementPtr json;
-    ASSERT_NO_THROW(json = parseDHCP6(config));
+    ASSERT_NO_THROW(json = parseDHCP6(config(valid_iface_)));
+    extractConfig(config("eth0"));
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
@@ -1264,6 +1277,7 @@ TEST_F(Dhcp6ParserTest, subnetInterfaceId) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
@@ -1414,6 +1428,7 @@ TEST_F(Dhcp6ParserTest, multiplePools) {
         "\"valid-lifetime\": 4000 }";
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     ASSERT_NO_THROW(status = configureDhcp6Server(srv_, json));
@@ -1494,6 +1509,7 @@ TEST_F(Dhcp6ParserTest, poolPrefixLen) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
 
@@ -1667,6 +1683,7 @@ TEST_F(Dhcp6ParserTest, pdPoolBasics) {
     // Convert the JSON string into Elements.
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     // Verify that DHCP6 configuration processing succeeds.
     // Returned value must be non-empty ConstElementPtr to config result.
@@ -1725,6 +1742,7 @@ TEST_F(Dhcp6ParserTest, pdPoolPrefixExclude) {
     // Convert the JSON string into Elements.
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     // Verify that DHCP6 configuration processing succeeds.
     // Returned value must be non-empty ConstElementPtr to config result.
@@ -1805,6 +1823,7 @@ TEST_F(Dhcp6ParserTest, pdPoolList) {
     // Convert the JSON string into Elements.
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     // Verify that DHCP6 configuration processing succeeds.
     // Returned value must be non-empty ConstElementPtr to config result.
@@ -1861,6 +1880,7 @@ TEST_F(Dhcp6ParserTest, subnetAndPrefixDelegated) {
     // Convert the JSON string into Elements.
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     // Verify that DHCP6 configuration processing succeeds.
     // Returned value must be non-empty ConstElementPtr to config result.
@@ -1989,6 +2009,7 @@ TEST_F(Dhcp6ParserTest, optionDefIpv6Address) {
         "}";
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseOPTION_DEF(config));
+    extractConfig(config);
 
     // Make sure that the particular option definition does not exist.
     OptionDefinitionPtr def = CfgMgr::instance().getStagingCfg()->
@@ -2053,6 +2074,7 @@ TEST_F(Dhcp6ParserTest, optionDefRecord) {
         "}";
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseOPTION_DEF(config));
+    extractConfig(config);
 
     // Make sure that the particular option definition does not exist.
     OptionDefinitionPtr def = CfgMgr::instance().getStagingCfg()->
@@ -2106,6 +2128,7 @@ TEST_F(Dhcp6ParserTest, optionDefMultiple) {
         "}";
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseOPTION_DEF(config));
+    extractConfig(config);
 
     // Make sure that the option definitions do not exist yet.
     ASSERT_FALSE(CfgMgr::instance().getStagingCfg()->
@@ -2212,6 +2235,7 @@ TEST_F(Dhcp6ParserTest, optionDefArray) {
         "}";
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseOPTION_DEF(config));
+    extractConfig(config);
 
     // Make sure that the particular option definition does not exist.
     OptionDefinitionPtr def = CfgMgr::instance().getStagingCfg()->
@@ -2252,6 +2276,7 @@ TEST_F(Dhcp6ParserTest, optionDefEncapsulate) {
         "}";
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseOPTION_DEF(config));
+    extractConfig(config);
 
     // Make sure that the particular option definition does not exist.
     OptionDefinitionPtr def = CfgMgr::instance().getStagingCfg()->
@@ -2551,6 +2576,7 @@ TEST_F(Dhcp6ParserTest, optionDataDefaultsGlobal) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
     checkResult(x, 0);
@@ -2623,6 +2649,7 @@ TEST_F(Dhcp6ParserTest, optionDataDefaultsSubnet) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
     checkResult(x, 0);
@@ -2713,6 +2740,7 @@ TEST_F(Dhcp6ParserTest, optionDataTwoSpaces) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
@@ -2789,6 +2817,7 @@ TEST_F(Dhcp6ParserTest, optionDataEncapsulate) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
@@ -2846,6 +2875,7 @@ TEST_F(Dhcp6ParserTest, optionDataEncapsulate) {
         "}";
 
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
     ASSERT_TRUE(status);
@@ -2905,6 +2935,7 @@ TEST_F(Dhcp6ParserTest, optionDataInMultipleSubnets) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
     checkResult(x, 0);
@@ -3005,6 +3036,7 @@ TEST_F(Dhcp6ParserTest, optionDataMultiplePools) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
     checkResult(x, 0);
@@ -3367,6 +3399,7 @@ TEST_F(Dhcp6ParserTest, vendorOptionsHex) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
@@ -3426,6 +3459,7 @@ TEST_F(Dhcp6ParserTest, vendorOptionsCsv) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
     ASSERT_TRUE(status);
@@ -3491,6 +3525,7 @@ TEST_F(Dhcp6ParserTest, DISABLED_stdOptionDataEncapsulate) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
@@ -3543,6 +3578,7 @@ TEST_F(Dhcp6ParserTest, DISABLED_stdOptionDataEncapsulate) {
         "}";
 
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
     ASSERT_TRUE(status);
     checkResult(status, 0);
@@ -3756,6 +3792,7 @@ TEST_F(Dhcp6ParserTest, selectedInterfaces) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
@@ -3794,6 +3831,7 @@ TEST_F(Dhcp6ParserTest, allInterfaces) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
@@ -3825,6 +3863,7 @@ TEST_F(Dhcp6ParserTest, subnetRelayInfo) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
@@ -3869,6 +3908,7 @@ TEST_F(Dhcp6ParserTest, classifySubnets) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
     checkResult(x, 0);
@@ -3964,6 +4004,7 @@ TEST_F(Dhcp6ParserTest, d2ClientConfig) {
     // Convert the JSON string to configuration elements.
     ConstElementPtr config;
     ASSERT_NO_THROW(config = parseDHCP6(config_str));
+    extractConfig(config_str);
 
     // Pass the configuration in for parsing.
     ConstElementPtr status;
@@ -4141,6 +4182,7 @@ TEST_F(Dhcp6ParserTest, reservations) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
     checkResult(x, 0);
@@ -4297,6 +4339,7 @@ TEST_F(Dhcp6ParserTest, reservationWithOptionDefinition) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
     checkResult(x, 0);
@@ -4453,15 +4496,17 @@ TEST_F(Dhcp6ParserTest, reservationBogus) {
 /// rfc4649 = remote-id, rfc4580 = subscriber-id).
 TEST_F(Dhcp6ParserTest, macSources1) {
 
+    string config = "{ " + genIfaceConfig() + ","
+        "\"mac-sources\": [ \"rfc6939\", \"rfc4649\", \"rfc4580\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [  ], "
+        "\"valid-lifetime\": 4000 }";
+
     ConstElementPtr json;
-    ASSERT_NO_THROW(json =
-        parseDHCP6("{ " + genIfaceConfig() + ","
-                   "\"mac-sources\": [ \"rfc6939\", \"rfc4649\", \"rfc4580\" ],"
-                   "\"preferred-lifetime\": 3000,"
-                   "\"rebind-timer\": 2000, "
-                   "\"renew-timer\": 1000, "
-                   "\"subnet6\": [  ], "
-                   "\"valid-lifetime\": 4000 }"));
+    ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
@@ -4480,16 +4525,18 @@ TEST_F(Dhcp6ParserTest, macSources1) {
 /// MAC/Hardware sources. This uses specific method names.
 TEST_F(Dhcp6ParserTest, macSources2) {
 
+    string config = "{ " + genIfaceConfig() + ","
+        "\"mac-sources\": [ \"client-link-addr-option\", \"remote-id\", "
+        "                   \"subscriber-id\"],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [  ], "
+        "\"valid-lifetime\": 4000 }";
+
     ConstElementPtr json;
-    ASSERT_NO_THROW(json =
-        parseDHCP6("{ " + genIfaceConfig() + ","
-                   "\"mac-sources\": [ \"client-link-addr-option\", \"remote-id\", "
-                   "                   \"subscriber-id\"],"
-                   "\"preferred-lifetime\": 3000,"
-                   "\"rebind-timer\": 2000, "
-                   "\"renew-timer\": 1000, "
-                   "\"subnet6\": [  ], "
-                   "\"valid-lifetime\": 4000 }"));
+    ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
@@ -4582,6 +4629,7 @@ TEST_F(Dhcp6ParserTest, hostReservationPerSubnet) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(HR_CONFIG));
+    extractConfig(HR_CONFIG);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
@@ -4659,15 +4707,17 @@ TEST_F(Dhcp6ParserTest, rsooNumbers) {
 /// Relay Supplied options (specified as names).
 TEST_F(Dhcp6ParserTest, rsooNames) {
 
+    string config = "{ " + genIfaceConfig() + ","
+        "\"relay-supplied-options\": [ \"dns-servers\", \"remote-id\" ],"
+        "\"preferred-lifetime\": 3000,"
+        "\"rebind-timer\": 2000, "
+        "\"renew-timer\": 1000, "
+        "\"subnet6\": [  ], "
+        "\"valid-lifetime\": 4000 }";
+
     ConstElementPtr json;
-    ASSERT_NO_THROW(json =
-        parseDHCP6("{ " + genIfaceConfig() + ","
-                   "\"relay-supplied-options\": [ \"dns-servers\", \"remote-id\" ],"
-                   "\"preferred-lifetime\": 3000,"
-                   "\"rebind-timer\": 2000, "
-                   "\"renew-timer\": 1000, "
-                   "\"subnet6\": [  ], "
-                   "\"valid-lifetime\": 4000 }"));
+    ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
@@ -4753,6 +4803,7 @@ TEST_F(Dhcp6ParserTest, declineTimerDefault) {
         "}";
     ConstElementPtr config;
     ASSERT_NO_THROW(config = parseDHCP6(config_txt));
+    extractConfig(config_txt);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, config));
@@ -4775,6 +4826,7 @@ TEST_F(Dhcp6ParserTest, dhcp4o6portDefault) {
         "}";
     ConstElementPtr config;
     ASSERT_NO_THROW(config = parseDHCP6(config_txt));
+    extractConfig(config_txt);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, config));
@@ -4797,6 +4849,7 @@ TEST_F(Dhcp6ParserTest, declineTimer) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
@@ -4851,6 +4904,7 @@ TEST_F(Dhcp6ParserTest, expiredLeasesProcessing) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
@@ -4929,6 +4983,7 @@ TEST_F(Dhcp6ParserTest, validClientClassDictionary) {
 
     ConstElementPtr json;
     ASSERT_NO_THROW(json = parseDHCP6(config));
+    extractConfig(config);
 
     ConstElementPtr status;
     EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
@@ -4975,6 +5030,7 @@ TEST_F(Dhcp6ParserTest, invalidClientClassDictionary) {
 // Test verifies that regular configuration does not provide any user context
 // in the address pool.
 TEST_F(Dhcp6ParserTest, poolUserContextMissing) {
+    extractConfig(PARSER_CONFIGS[0]);
     PoolPtr pool;
     getPool(string(PARSER_CONFIGS[0]), 0, 0, Lease::TYPE_NA, pool);
     ASSERT_TRUE(pool);
@@ -4984,6 +5040,7 @@ TEST_F(Dhcp6ParserTest, poolUserContextMissing) {
 // Test verifies that it's possible to specify empty user context in the
 // address pool.
 TEST_F(Dhcp6ParserTest, poolUserContextEmpty) {
+    extractConfig(PARSER_CONFIGS[1]);
     PoolPtr pool;
     getPool(string(PARSER_CONFIGS[1]), 0, 0, Lease::TYPE_NA, pool);
     ASSERT_TRUE(pool);
@@ -4998,6 +5055,7 @@ TEST_F(Dhcp6ParserTest, poolUserContextEmpty) {
 // Test verifies that it's possible to specify parameters in the user context
 // in the address pool.
 TEST_F(Dhcp6ParserTest, poolUserContextlw4over6) {
+    extractConfig(PARSER_CONFIGS[2]);
     PoolPtr pool;
     getPool(string(PARSER_CONFIGS[2]), 0, 0, Lease::TYPE_NA, pool);
     ASSERT_TRUE(pool);
@@ -5037,6 +5095,7 @@ TEST_F(Dhcp6ParserTest, poolUserContextlw4over6) {
 // Test verifies that it's possible to specify parameters in the user context
 // in the min-max address pool.
 TEST_F(Dhcp6ParserTest, poolMinMaxUserContext) {
+    extractConfig(PARSER_CONFIGS[3]);
     PoolPtr pool;
     getPool(string(PARSER_CONFIGS[3]), 0, 0, Lease::TYPE_NA, pool);
     ASSERT_TRUE(pool);
@@ -5076,6 +5135,7 @@ TEST_F(Dhcp6ParserTest, poolMinMaxUserContext) {
 // Test verifies that regular configuration does not provide any user context
 // in the address pool.
 TEST_F(Dhcp6ParserTest, pdPoolUserContextMissing) {
+    extractConfig(PARSER_CONFIGS[4]);
     PoolPtr pool;
     getPool(string(PARSER_CONFIGS[4]), 0, 0, Lease::TYPE_PD, pool);
     ASSERT_TRUE(pool);
@@ -5085,6 +5145,7 @@ TEST_F(Dhcp6ParserTest, pdPoolUserContextMissing) {
 // Test verifies that it's possible to specify empty user context in the
 // address pool.
 TEST_F(Dhcp6ParserTest, pdPoolUserContextEmpty) {
+    extractConfig(PARSER_CONFIGS[5]);
     PoolPtr pool;
     getPool(string(PARSER_CONFIGS[5]), 0, 0, Lease::TYPE_PD, pool);
     ASSERT_TRUE(pool);
@@ -5099,6 +5160,7 @@ TEST_F(Dhcp6ParserTest, pdPoolUserContextEmpty) {
 // Test verifies that it's possible to specify parameters in the user context
 // in the address pool.
 TEST_F(Dhcp6ParserTest, pdPoolUserContextlw4over6) {
+    extractConfig(PARSER_CONFIGS[6]);
     PoolPtr pool;
     getPool(string(PARSER_CONFIGS[6]), 0, 0, Lease::TYPE_PD, pool);
     ASSERT_TRUE(pool);

+ 1 - 0
src/bin/dhcp6/tests/dhcp6_test_utils.cc

@@ -66,6 +66,7 @@ Dhcpv6SrvTest::Dhcpv6SrvTest()
     subnet_->addPool(pool_);
 
     isc::dhcp::CfgMgr::instance().clear();
+    CfgMgr::instance().setFamily(AF_INET6);
     isc::dhcp::CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->add(subnet_);
     isc::dhcp::CfgMgr::instance().commit();
 

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

@@ -89,6 +89,7 @@ libkea_dhcpsrv_la_SOURCES += cfg_4o6.cc cfg_4o6.h
 libkea_dhcpsrv_la_SOURCES += cfg_db_access.cc cfg_db_access.h
 libkea_dhcpsrv_la_SOURCES += cfg_duid.cc cfg_duid.h
 libkea_dhcpsrv_la_SOURCES += cfg_hosts.cc cfg_hosts.h
+libkea_dhcpsrv_la_SOURCES += cfg_hosts_util.cc cfg_hosts_util.h
 libkea_dhcpsrv_la_SOURCES += cfg_iface.cc cfg_iface.h
 libkea_dhcpsrv_la_SOURCES += cfg_expiration.cc cfg_expiration.h
 libkea_dhcpsrv_la_SOURCES += cfg_host_operations.cc cfg_host_operations.h

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

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -6,11 +6,17 @@
 
 #include <config.h>
 #include <dhcpsrv/cfg_hosts.h>
+#include <dhcpsrv/cfg_hosts_util.h>
 #include <dhcpsrv/hosts_log.h>
+#include <dhcpsrv/cfgmgr.h>
 #include <exceptions/exceptions.h>
+#include <util/encode/hex.h>
 #include <ostream>
+#include <string>
+#include <vector>
 
 using namespace isc::asiolink;
+using namespace isc::data;
 
 namespace isc {
 namespace dhcp {
@@ -667,5 +673,144 @@ CfgHosts::add6(const HostPtr& host) {
     }
 }
 
+ElementPtr
+CfgHosts::toElement() const {
+    uint16_t family = CfgMgr::instance().getFamily();
+    if (family == AF_INET) {
+        return (toElement4());
+    } else if (family == AF_INET6) {
+        return (toElement6());
+    } else {
+        isc_throw(ToElementError, "CfgHosts::toElement: unknown "
+                  "address family: " << family);
+    }
+}
+
+ElementPtr
+CfgHosts::toElement4() const {
+    CfgHostsList result;
+    // Iterate using arbitrary the index 0
+    const HostContainerIndex0& idx = hosts_.get<0>();
+    for (HostContainerIndex0::const_iterator host = idx.begin();
+         host != idx.end(); ++host) {
+        // Get the subnet ID
+        SubnetID subnet_id = (*host)->getIPv4SubnetID();
+        // Prepare the map
+        ElementPtr map = Element::createMap();
+        // Set the identifier
+        Host::IdentifierType id_type = (*host)->getIdentifierType();
+        if (id_type == Host::IDENT_HWADDR) {
+            HWAddrPtr hwaddr = (*host)->getHWAddress();
+            map->set("hw-address", Element::create(hwaddr->toText(false)));
+        } else if (id_type == Host::IDENT_DUID) {
+            DuidPtr duid = (*host)->getDuid();
+            map->set("duid", Element::create(duid->toText()));
+        } else if (id_type == Host::IDENT_CIRCUIT_ID) {
+            const std::vector<uint8_t>& bin = (*host)->getIdentifier();
+            std::string circuit_id = util::encode::encodeHex(bin);
+            map->set("circuit-id", Element::create(circuit_id));
+        } else if (id_type == Host::IDENT_CLIENT_ID) {
+            const std::vector<uint8_t>& bin = (*host)->getIdentifier();
+            std::string client_id = util::encode::encodeHex(bin);
+            map->set("client-id", Element::create(client_id));
+        } else {
+            isc_throw(ToElementError, "invalid DUID type: " << id_type);
+        }
+        // Set the reservation
+        const IOAddress& address = (*host)->getIPv4Reservation();
+        map->set("ip-address", Element::create(address.toText()));
+        // Set the hostname
+        const std::string& hostname = (*host)->getHostname();
+        map->set("hostname", Element::create(hostname));
+        // Set next-server
+        const IOAddress& next_server = (*host)->getNextServer();
+        map->set("next-server", Element::create(next_server.toText()));
+        // Set server-hostname
+        const std::string& server_hostname = (*host)->getServerHostname();
+        map->set("server-hostname", Element::create(server_hostname));
+        // Set boot-file-name
+        const std::string& boot_file_name = (*host)->getBootFileName();
+        map->set("boot-file-name", Element::create(boot_file_name));
+        // Set client-classes
+        const ClientClasses& cclasses = (*host)->getClientClasses4();
+        ElementPtr classes = Element::createList();
+        for (ClientClasses::const_iterator cclass = cclasses.cbegin();
+             cclass != cclasses.end(); ++cclass) {
+            classes->add(Element::create(*cclass));
+        }
+        map->set("client-classes", classes);
+        // Set option-data
+        ConstCfgOptionPtr opts = (*host)->getCfgOption4();
+        map->set("option-data", opts->toElement());
+        // Push the map on the list
+        result.add(subnet_id, map);
+    }
+    return (result.externalize());
+}
+
+ElementPtr
+CfgHosts::toElement6() const {
+    CfgHostsList result;
+    // Iterate using arbitrary the index 0
+    const HostContainerIndex0& idx = hosts_.get<0>();
+    for (HostContainerIndex0::const_iterator host = idx.begin();
+         host != idx.end(); ++host) {
+        // Get the subnet ID
+        SubnetID subnet_id = (*host)->getIPv6SubnetID();
+        // Prepare the map
+        ElementPtr map = Element::createMap();
+        // Set the identifier
+        Host::IdentifierType id_type = (*host)->getIdentifierType();
+        if (id_type == Host::IDENT_HWADDR) {
+            HWAddrPtr hwaddr = (*host)->getHWAddress();
+            map->set("hw-address", Element::create(hwaddr->toText(false)));
+        } else if (id_type == Host::IDENT_DUID) {
+            DuidPtr duid = (*host)->getDuid();
+            map->set("duid", Element::create(duid->toText()));
+        } else if (id_type == Host::IDENT_CIRCUIT_ID) {
+            isc_throw(ToElementError, "unexpected circuit-id DUID type");
+        } else if (id_type == Host::IDENT_CLIENT_ID) {
+            isc_throw(ToElementError, "unexpected client-id DUID type");
+        } else {
+            isc_throw(ToElementError, "invalid DUID type: " << id_type);
+        }
+        // Set reservations (ip-addresses)
+        IPv6ResrvRange na_resv =
+            (*host)->getIPv6Reservations(IPv6Resrv::TYPE_NA);
+        ElementPtr resvs = Element::createList();
+        for (IPv6ResrvIterator resv = na_resv.first;
+             resv != na_resv.second; ++resv) {
+            resvs->add(Element::create(resv->second.toText()));
+        }
+        map->set("ip-addresses", resvs);
+        // Set reservations (prefixes)
+        IPv6ResrvRange pd_resv =
+                (*host)->getIPv6Reservations(IPv6Resrv::TYPE_PD);
+        resvs = Element::createList();
+        for (IPv6ResrvIterator resv = pd_resv.first;
+             resv != pd_resv.second; ++resv) {
+            resvs->add(Element::create(resv->second.toText()));
+        }
+        map->set("prefixes", resvs);
+        // Set the hostname
+        const std::string& hostname = (*host)->getHostname();
+        map->set("hostname", Element::create(hostname));
+        // Set client-classes
+        const ClientClasses& cclasses = (*host)->getClientClasses6();
+        ElementPtr classes = Element::createList();
+        for (ClientClasses::const_iterator cclass = cclasses.cbegin();
+             cclass != cclasses.end(); ++cclass) {
+            classes->add(Element::create(*cclass));
+        }
+        map->set("client-classes", classes);
+        // Set option-data
+        ConstCfgOptionPtr opts = (*host)->getCfgOption6();
+        map->set("option-data", opts->toElement());
+        // Push the map on the list
+        result.add(subnet_id, map);
+    }
+    return (result.externalize());
+}
+
 } // end of namespace isc::dhcp
 } // end of namespace isc

+ 32 - 2
src/lib/dhcpsrv/cfg_hosts.h

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -8,6 +8,7 @@
 #define CFG_HOSTS_H
 
 #include <asiolink/io_address.h>
+#include <cc/cfg_to_element.h>
 #include <dhcp/duid.h>
 #include <dhcp/hwaddr.h>
 #include <dhcpsrv/base_host_data_source.h>
@@ -35,7 +36,8 @@ namespace dhcp {
 /// when the new configuration is applied for the server. The reservations
 /// are retrieved by the @c HostMgr class when the server is allocating or
 /// renewing an address or prefix for the particular client.
-class CfgHosts : public BaseHostDataSource, public WritableHostDataSource {
+class CfgHosts : public BaseHostDataSource, public WritableHostDataSource,
+                 public isc::data::CfgToElement {
 public:
 
     /// @brief Destructor.
@@ -329,6 +331,24 @@ public:
         return (std::string("configuration file"));
     }
 
+    /// @brief Unparse a configuration objet
+    ///
+    /// host reservation lists are not autonomous so they are
+    /// not returned directly but with the subnet where they are
+    /// declared as:
+    /// @code
+    /// [
+    ///   { "id": 123, "reservations": [ <resv1>, <resv2> ] },
+    ///   { "id": 456, "reservations": [ <resv3 ] },
+    ///   ...
+    /// ]
+    /// @endcode
+    ///
+    /// @ref isc::dhcp::CfgHostsList can be used to handle this
+    ///
+    /// @return a pointer to unparsed configuration
+    isc::data::ElementPtr toElement() const;
+
 private:
 
     /// @brief Returns @c Host objects for the specific identifier and type.
@@ -491,6 +511,16 @@ private:
     /// - IPv6 address
     /// - IPv6 prefix
     HostContainer6 hosts6_;
+
+    /// @brief Unparse a configuration objet (DHCPv4 reservations)
+    ///
+    /// @return a pointer to unparsed configuration
+    isc::data::ElementPtr toElement4() const;
+
+    /// @brief Unparse a configuration objet (DHCPv6 reservations)
+    ///
+    /// @return a pointer to unparsed configuration
+    isc::data::ElementPtr toElement6() const;
 };
 
 /// @name Pointers to the @c CfgHosts objects.

+ 94 - 0
src/lib/dhcpsrv/cfg_hosts_util.cc

@@ -0,0 +1,94 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <cc/data.h>
+#include <dhcpsrv/subnet_id.h>
+#include <dhcpsrv/cfg_hosts_util.h>
+#include <exceptions/exceptions.h>
+#include <boost/pointer_cast.hpp>
+
+using namespace isc::data;
+
+namespace isc {
+namespace dhcp {
+
+void CfgHostsList::internalize(ConstElementPtr list) {
+    if (!list) {
+        isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+                  "argument is NULL");
+    }
+    if (list->getType() != Element::list) {
+        isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+                  "argument is not a list Element");
+    }
+    for (size_t i = 0; i < list->size(); ++i) {
+        ConstElementPtr item = list->get(i);
+        if (!item) {
+            isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+                      "null pointer from the list at " << i);
+        }
+        if (item->getType() != Element::map) {
+            isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+                      "not a map from the list at " << i);
+        }
+        if (item->size() != 2) {
+            isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+                      "bad map size from the list at " << i);
+        }
+        ConstElementPtr id = item->get("id");
+        if (!id) {
+            isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+                      "no id from a map at " << i);
+        }
+        if (id->getType() != Element::integer) {
+            isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+                      "not integer id from a map at " <<i);
+        }
+        SubnetID subnet_id = static_cast<SubnetID>(id->intValue());
+        ConstElementPtr resvs = item->get("reservations");
+        if (!resvs) {
+            isc_throw(BadValue, "internal error: CfgHostsList::internalize: "
+                      "no reservations for subnet ID " << subnet_id);
+        }
+        map_.insert(std::make_pair(subnet_id, 
+                                   boost::const_pointer_cast<Element>(resvs)));
+    }
+}
+
+ElementPtr CfgHostsList::externalize() const {
+    ElementPtr result = Element::createList();
+    for (CfgHostsMap::const_iterator item = map_.begin();
+         item != map_.end(); ++item) {
+        ElementPtr pair = Element::createMap();
+        pair->set("id", Element::create(static_cast<int64_t>(item->first)));
+        pair->set("reservations", item->second);
+        result->add(pair);
+    }
+    return (result);
+}
+
+void CfgHostsList::add(SubnetID id, isc::data::ElementPtr resv) {
+    CfgHostsMap::iterator item = map_.find(id);
+    if (item != map_.end()) {
+        item->second->add(resv);
+    } else {
+        ElementPtr resvs = Element::createList();
+        resvs->add(resv);
+        map_.insert(std::make_pair(id, resvs));
+    }
+}
+
+ConstElementPtr CfgHostsList::get(SubnetID id) const {
+    CfgHostsMap::const_iterator item = map_.find(id);
+    if (item != map_.end()) {
+        return (item->second);
+    } else {
+        return (Element::createList());
+    }
+}
+
+}
+}

+ 53 - 0
src/lib/dhcpsrv/cfg_hosts_util.h

@@ -0,0 +1,53 @@
+// Copyright (C) 2017 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#ifndef CFG_HOSTS_UTIL_H
+#define CFG_HOSTS_UTIL_H
+
+#include <cc/data.h>
+#include <dhcpsrv/subnet_id.h>
+
+namespace isc {
+namespace dhcp {
+
+/// @brief Utility class to represent host reservation configurations
+/// internally as a map keyed by subnet IDs, externally as a list Element.
+class CfgHostsList {
+public:
+
+    /// The type of the internal map
+    typedef std::map<SubnetID, isc::data::ElementPtr> CfgHostsMap;
+
+    /// @brief Internalize a list Element
+    ///
+    /// This method gets a list Element and builds the internal map from it.
+    ///
+    /// @param list the list Element
+    void internalize(isc::data::ConstElementPtr list);
+
+    /// @brief Externalize the map to a list Element
+    ///
+    /// @return a list Element representing all host reservations
+    isc::data::ElementPtr externalize() const;
+
+    /// @brief Add a host reservation to the map
+    void add(SubnetID id, isc::data::ElementPtr resv);
+
+    /// @brief Return the host reservations for a subnet ID
+    ///
+    /// @param id the subnet ID
+    /// @return a list Element with host reservations
+    isc::data::ConstElementPtr get(SubnetID id) const;
+
+private:
+    /// @brief The internal map
+    CfgHostsMap map_;
+};
+
+}
+}
+
+#endif // CFG_HOSTS_UTIL_H

+ 2 - 4
src/lib/dhcpsrv/cfg_subnets4.cc

@@ -332,9 +332,7 @@ CfgSubnets4::toElement() const {
             if (!isNull(context)) {
                 pool_map->set("user-context", context);
             }
-            // Set pool options
-            ConstCfgOptionPtr opts = (*pool)->getCfgOption();
-            pool_map->set("option-data", opts->toElement());
+            // Set pool options (not yet supported)
             // Push on the pool list
             pool_list->add(pool_map);
         }
@@ -365,7 +363,7 @@ CfgSubnets4::toElement() const {
                  Element::create((*subnet)->getSiaddr().toText()));
         // Set DHCP4o6
         const Cfg4o6& d4o6 = (*subnet)->get4o6();
-        merge(map, d4o6.toElement());
+        isc::data::merge(map, d4o6.toElement());
         // Set client-class
         const ClientClasses& cclasses = (*subnet)->getClientClasses();
         if (cclasses.size() > 1) {

+ 30 - 6
src/lib/dhcpsrv/srv_config.cc

@@ -8,6 +8,7 @@
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/srv_config.h>
 #include <dhcpsrv/lease_mgr_factory.h>
+#include <dhcpsrv/cfg_hosts_util.h>
 #include <log/logger_manager.h>
 #include <log/logger_specification.h>
 #include <dhcp/pkt.h> // Needed for HWADDR_SOURCE_*
@@ -231,13 +232,29 @@ SrvConfig::toElement() const {
     ConstElementPtr option_data = cfg_option_->toElement();
     dhcp->set("option-data", option_data);
     // Set subnets
+    ConstElementPtr subnets;
+    if (family == AF_INET) {
+        subnets = cfg_subnets4_->toElement();
+    } else {
+        subnets = cfg_subnets6_->toElement();
+    }
+    // Insert reservations
+    CfgHostsList resv_list;
+    resv_list.internalize(cfg_hosts_->toElement());
+    const std::vector<ElementPtr>& sn_list = subnets->listValue();
+    for (std::vector<ElementPtr>::const_iterator subnet = sn_list.begin();
+         subnet != sn_list.end(); ++subnet) {
+        ConstElementPtr id = (*subnet)->get("id");
+        if (isNull(id)) {
+            isc_throw(ToElementError, "subnet has no id");
+        }
+        SubnetID subnet_id = id->intValue();
+        ConstElementPtr resvs = resv_list.get(subnet_id);
+        (*subnet)->set("reservations", resvs);
+    }
     if (family == AF_INET) {
-        ConstElementPtr subnets = cfg_subnets4_->toElement();
-        // @todo Insert reservations
         dhcp->set("subnet4", subnets);
     } else {
-        ConstElementPtr subnets = cfg_subnets6_->toElement();
-        // @todo Insert reservations
         dhcp->set("subnet6", subnets);
     }
     // Set relay-supplied-options (DHCPv6)
@@ -256,7 +273,11 @@ SrvConfig::toElement() const {
     dhcp->set("lease-database", lease_db.toElement());
     // Set hosts-database
     CfgHostDbAccess host_db(*cfg_db_access_);
-    dhcp->set("hosts-database", host_db.toElement());
+    // @todo accept empty map
+    ConstElementPtr hosts_database = host_db.toElement();
+    if (hosts_database->size() > 0) {
+        dhcp->set("hosts-database", hosts_database);
+    }
     // Set host-reservation-identifiers
     ConstElementPtr host_ids;
     if (family == AF_INET) {
@@ -275,7 +296,10 @@ SrvConfig::toElement() const {
     }
     // Set client-classes
     ConstElementPtr client_classes = class_dictionary_->toElement();
-    dhcp->set("client-classes", client_classes);
+    // @todo accept empty list
+    if (!client_classes->empty()) {
+        dhcp->set("client-classes", client_classes);
+    }
     // Set hooks-libraries
     ConstElementPtr hooks_libs = hooks_config_.toElement();
     dhcp->set("hooks-libraries", hooks_libs);

+ 195 - 7
src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2014-2017 Internet Systems Consortium, Inc. ("ISC")
 //
 // This Source Code Form is subject to the terms of the Mozilla Public
 // License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -9,7 +9,9 @@
 #include <dhcp/duid.h>
 #include <dhcp/hwaddr.h>
 #include <dhcpsrv/cfg_hosts.h>
+#include <dhcpsrv/cfg_hosts_util.h>
 #include <dhcpsrv/host.h>
+#include <dhcpsrv/cfgmgr.h>
 #include <gtest/gtest.h>
 #include <sstream>
 #include <set>
@@ -78,7 +80,7 @@ CfgHostsTest::CfgHostsTest() {
 
     const uint32_t addra_template = 0xc0000205; // 192.0.2.5
     const uint32_t addrb_template = 0xc00a020a; // 192.10.2.10
-    for (int i = 0; i < 50; ++i) {
+    for (unsigned i = 0; i < 50; ++i) {
         IOAddress addra(addra_template + i);
         addressesa_.push_back(addra);
         IOAddress addrb(addrb_template + i);
@@ -102,7 +104,7 @@ TEST_F(CfgHostsTest, getAllNonRepeatingHosts) {
     CfgHosts cfg;
     // Add 25 hosts identified by HW address and 25 hosts identified by
     // DUID. They are added to different subnets.
-    for (int i = 0; i < 25; ++i) {
+    for (unsigned i = 0; i < 25; ++i) {
         cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
                                  "hw-address",
                                  SubnetID(i % 10 + 1), SubnetID(i % 5 + 1),
@@ -151,7 +153,7 @@ TEST_F(CfgHostsTest, getAllNonRepeatingHosts) {
 TEST_F(CfgHostsTest, getAllRepeatingHosts) {
     CfgHosts cfg;
     // Add hosts.
-    for (int i = 0; i < 25; ++i) {
+    for (unsigned i = 0; i < 25; ++i) {
         // Add two hosts, using the same HW address to two distinct subnets.
         cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
                                  "hw-address",
@@ -172,7 +174,7 @@ TEST_F(CfgHostsTest, getAllRepeatingHosts) {
     }
 
     // Verify that hosts can be retrieved.
-    for (int i = 0; i < 25; ++i) {
+    for (unsigned i = 0; i < 25; ++i) {
         // Get host by HW address. The DUID is non-null but the reservation
         // should be returned for the HW address because there are no
         // reservations for the DUIDs from the range of 25 to 49.
@@ -205,7 +207,7 @@ TEST_F(CfgHostsTest, getAllRepeatingHosts) {
 TEST_F(CfgHostsTest, getAll4ByAddress) {
     CfgHosts cfg;
     // Add hosts.
-    for (int i = 0; i < 25; ++i) {
+    for (unsigned i = 0; i < 25; ++i) {
         // Add host identified by the HW address.
         cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
                                  "hw-address",
@@ -268,6 +270,90 @@ TEST_F(CfgHostsTest, get4) {
     }
 }
 
+// This test checks that the DHCPv4 reservations can be unparsed
+TEST_F(CfgHostsTest, unparsed4) {
+    CfgMgr::instance().setFamily(AF_INET);
+    CfgHosts cfg;
+    CfgHostsList list;
+    // Add hosts.
+    for (unsigned i = 0; i < 25; ++i) {
+        // Add host identified by HW address.
+        cfg.add(HostPtr(new Host(hwaddrs_[i]->toText(false),
+                                 "hw-address",
+                                 SubnetID(1 + i), SubnetID(13),
+                                 increase(IOAddress("192.0.2.5"), i))));
+
+        // Add host identified by DUID.
+        cfg.add(HostPtr(new Host(duids_[i]->toText(), "duid",
+                                 SubnetID(1 + i), SubnetID(13),
+                                 increase(IOAddress("192.0.2.100"), i))));
+    }
+
+    using namespace isc::data;
+    ConstElementPtr cfg_unparsed;
+    ASSERT_NO_THROW(cfg_unparsed = cfg.toElement());
+    ASSERT_NO_THROW(list.internalize(cfg_unparsed));
+    for (unsigned i = 0; i < 25; ++i) {
+        ConstElementPtr unparsed = list.get(SubnetID(1 + i));
+        ASSERT_TRUE(unparsed);
+        ASSERT_EQ(Element::list, unparsed->getType());
+        EXPECT_EQ(2, unparsed->size());
+        ASSERT_NE(0, unparsed->size());
+
+        // Check by HW address entries
+        bool checked_hw = false;
+        for (unsigned j = 0; j < unparsed->size(); ++j) {
+            ConstElementPtr host = unparsed->get(j);
+            ASSERT_TRUE(host);
+            ASSERT_EQ(Element::map, host->getType());
+            if (!host->contains("hw-address")) {
+                continue;
+            }
+            checked_hw = true;
+            // Not both hw-address and duid
+            EXPECT_FALSE(host->contains("duid"));
+            // Check the HW address
+            ConstElementPtr hw = host->get("hw-address");
+            ASSERT_TRUE(hw);
+            ASSERT_EQ(Element::string, hw->getType());
+            EXPECT_EQ(hwaddrs_[i]->toText(false), hw->stringValue());
+            // Check the reservation
+            ConstElementPtr resv = host->get("ip-address");
+            ASSERT_TRUE(resv);
+            ASSERT_EQ(Element::string, resv->getType());
+            EXPECT_EQ(increase(IOAddress("192.0.2.5"), i),
+                          IOAddress(resv->stringValue()));
+        }
+        ASSERT_TRUE(checked_hw);
+
+        // Check by DUID entries
+        bool checked_duid = false;
+        for (unsigned j = 0; j < unparsed->size(); ++j) {
+            ConstElementPtr host = unparsed->get(j);
+            ASSERT_TRUE(host);
+            ASSERT_EQ(Element::map, host->getType());
+            if (!host->contains("duid")) {
+                continue;
+            }
+            checked_duid = true;
+            // Not both hw-address and duid
+            EXPECT_FALSE(host->contains("hw-address"));
+            // Check the DUID
+            ConstElementPtr duid = host->get("duid");
+            ASSERT_TRUE(duid);
+            ASSERT_EQ(Element::string, duid->getType());
+            EXPECT_EQ(duids_[i]->toText(), duid->stringValue());
+            // Check the reservation
+            ConstElementPtr resv = host->get("ip-address");
+            ASSERT_TRUE(resv);
+            ASSERT_EQ(Element::string, resv->getType());
+            EXPECT_EQ(increase(IOAddress("192.0.2.100"), i),
+                      IOAddress(resv->stringValue()));
+        }
+        ASSERT_TRUE(checked_duid);
+    }
+}
+
 // This test checks that the reservations can be retrieved for the particular
 // host connected to the specific IPv6 subnet (by subnet id).
 TEST_F(CfgHostsTest, get6) {
@@ -319,6 +405,108 @@ TEST_F(CfgHostsTest, get6) {
     }
 }
 
+// This test checks that the DHCPv6 reservations can be unparsed
+TEST_F(CfgHostsTest, unparse6) {
+    CfgMgr::instance().setFamily(AF_INET6);
+    CfgHosts cfg;
+    CfgHostsList list;
+    // Add hosts.
+    for (unsigned i = 0; i < 25; ++i) {
+        // Add host identified by HW address.
+        HostPtr host = HostPtr(new Host(hwaddrs_[i]->toText(false),
+                                        "hw-address",
+                                        SubnetID(10), SubnetID(1 + i),
+                                        IOAddress("0.0.0.0")));
+        host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                       increase(IOAddress("2001:db8:1::1"),
+                                                i)));
+        cfg.add(host);
+
+        // Add host identified by DUID.
+        host = HostPtr(new Host(duids_[i]->toText(), "duid",
+                                SubnetID(10), SubnetID(1 + i),
+                                IOAddress("0.0.0.0")));
+        host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,
+                                       increase(IOAddress("2001:db8:2::1"),
+                                                i)));
+        cfg.add(host);
+    }
+
+    using namespace isc::data;
+    ConstElementPtr cfg_unparsed;
+    ASSERT_NO_THROW(cfg_unparsed = cfg.toElement());
+    ASSERT_NO_THROW(list.internalize(cfg_unparsed));
+    for (unsigned i = 0; i < 25; ++i) {
+        ConstElementPtr unparsed = list.get(SubnetID(1 + i));
+        ASSERT_TRUE(unparsed);
+        ASSERT_EQ(Element::list, unparsed->getType());
+        EXPECT_EQ(2, unparsed->size());
+        ASSERT_NE(0, unparsed->size());
+
+        // Check by HW address entries
+        bool checked_hw = false;
+        for (unsigned j = 0; j < unparsed->size(); ++j) {
+            ConstElementPtr host = unparsed->get(j);
+            ASSERT_TRUE(host);
+            ASSERT_EQ(Element::map, host->getType());
+            if (!host->contains("hw-address")) {
+                continue;
+            }
+            checked_hw = true;
+            // Not both hw-address and duid
+            EXPECT_FALSE(host->contains("duid"));
+            // Check the HW address
+            ConstElementPtr hw = host->get("hw-address");
+            ASSERT_TRUE(hw);
+            ASSERT_EQ(Element::string, hw->getType());
+            EXPECT_EQ(hwaddrs_[i]->toText(false), hw->stringValue());
+            // Check the reservation
+            ConstElementPtr resvs = host->get("ip-addresses");
+            ASSERT_TRUE(resvs);
+            ASSERT_EQ(Element::list, resvs->getType());
+            EXPECT_EQ(1, resvs->size());
+            ASSERT_GE(1, resvs->size());
+            ConstElementPtr resv = resvs->get(0);
+            ASSERT_TRUE(resv);
+            ASSERT_EQ(Element::string, resv->getType());
+            EXPECT_EQ(increase(IOAddress("2001:db8:1::1"), i),
+                          IOAddress(resv->stringValue()));
+        }
+        ASSERT_TRUE(checked_hw);
+
+        // Check by DUID entries
+        bool checked_duid = false;
+        for (unsigned j = 0; j < unparsed->size(); ++j) {
+            ConstElementPtr host = unparsed->get(j);
+            ASSERT_TRUE(host);
+            ASSERT_EQ(Element::map, host->getType());
+            if (!host->contains("duid")) {
+                continue;
+            }
+            checked_duid = true;
+            // Not both hw-address and duid
+            EXPECT_FALSE(host->contains("hw-address"));
+            // Check the DUID
+            ConstElementPtr duid = host->get("duid");
+            ASSERT_TRUE(duid);
+            ASSERT_EQ(Element::string, duid->getType());
+            EXPECT_EQ(duids_[i]->toText(), duid->stringValue());
+            // Check the reservation
+            ConstElementPtr resvs = host->get("ip-addresses");
+            ASSERT_TRUE(resvs);
+            ASSERT_EQ(Element::list, resvs->getType());
+            EXPECT_EQ(1, resvs->size());
+            ASSERT_GE(1, resvs->size());
+            ConstElementPtr resv = resvs->get(0);
+            ASSERT_TRUE(resv);
+            ASSERT_EQ(Element::string, resv->getType());
+            EXPECT_EQ(increase(IOAddress("2001:db8:2::1"), i),
+                      IOAddress(resv->stringValue()));
+        }
+        ASSERT_TRUE(checked_duid);
+    }
+}
+
 // This test checks that the IPv6 reservations can be retrieved for a particular
 // (subnet-id, address) tuple.
 TEST_F(CfgHostsTest, get6ByAddr) {
@@ -365,7 +553,7 @@ TEST_F(CfgHostsTest, get6MultipleAddrs) {
                                         IOAddress("0.0.0.0")));
 
         // Generate 5 unique addresses for this host.
-        for (int j = 0; j < 5; ++j) {
+        for (unsigned j = 0; j < 5; ++j) {
             std::stringstream address_stream;
             address_stream << "2001:db8:" << i << "::" << j;
             host->addReservation(IPv6Resrv(IPv6Resrv::TYPE_NA,

+ 2 - 4
src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc

@@ -544,11 +544,9 @@ TEST(CfgSubnets4Test, unparsePool) {
         "    \"option-data\": [],\n"
         "    \"pools\": [\n"
         "        {\n"
-        "            \"pool\": \"192.0.2.1-192.0.2.10\",\n"
-        "            \"option-data\": []\n"
+        "            \"pool\": \"192.0.2.1-192.0.2.10\"\n"
         "        },{\n"
-        "            \"pool\": \"192.0.2.64/26\",\n"
-        "            \"option-data\": []\n"
+        "            \"pool\": \"192.0.2.64/26\"\n"
         "        }\n"
         "    ]\n"
         "} ]\n";

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

@@ -280,6 +280,7 @@ public:
 
     void clear() {
         CfgMgr::instance().setVerbose(false);
+        CfgMgr::instance().setFamily(AF_INET);
         CfgMgr::instance().clear();
         LeaseMgrFactory::destroy();
     }

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

@@ -14,11 +14,14 @@
 #include <dhcp/option4_addrlst.h>
 #include <dhcp/option6_addrlst.h>
 #include <dhcpsrv/cfgmgr.h>
+#include <dhcpsrv/cfg_hosts_util.h>
 #include <dhcpsrv/host.h>
 #include <dhcpsrv/parsers/dhcp_parsers.h>
 #include <dhcpsrv/parsers/host_reservation_parser.h>
 #include <dhcpsrv/testutils/config_result_check.h>
+#include <testutils/test_to_element.h>
 #include <boost/pointer_cast.hpp>
+#include <boost/algorithm/string.hpp>
 #include <gtest/gtest.h>
 #include <iterator>
 #include <sstream>
@@ -28,6 +31,7 @@
 using namespace isc::asiolink;
 using namespace isc::data;
 using namespace isc::dhcp;
+using namespace isc::test;
 
 namespace {
 
@@ -216,6 +220,77 @@ HostReservationParserTest::TearDown() {
     CfgMgr::instance().clear();
 }
 
+/// @brief class of subnet_id reservations
+class CfgHostsSubnet : public CfgToElement {
+public:
+    /// @brief constructor
+    CfgHostsSubnet(ConstCfgHostsPtr hosts, SubnetID id)
+        : hosts_(hosts), id_(id) { }
+
+    /// @brief unparse method
+    ElementPtr toElement() const;
+
+private:
+    /// @brief the host reservation configuration
+    ConstCfgHostsPtr hosts_;
+
+    /// @brief the subnet ID
+    SubnetID id_;
+};
+
+ElementPtr
+CfgHostsSubnet::toElement() const {
+    CfgHostsList list;
+    try {
+        list.internalize(hosts_->toElement());
+    } catch (const std::exception& ex) {
+        ADD_FAILURE() << "CfgHostsSubnet::toElement: " << ex.what();
+    }
+    ElementPtr result = boost::const_pointer_cast<Element>(list.get(id_));
+
+    // Strip
+    for (size_t i = 0; i < result->size(); ++i) {
+        ElementPtr resv = result->getNonConst(i);
+        ConstElementPtr ip_address = resv->get("ip-address");
+        if (ip_address && (ip_address->stringValue() == "0.0.0.0")) {
+            resv->remove("ip-address");
+        }
+        ConstElementPtr ip_addresses = resv->get("ip-addresses");
+        if (ip_addresses && ip_addresses->empty()) {
+            resv->remove("ip-addresses");
+        }
+        ConstElementPtr prefixes = resv->get("prefixes");
+        if (prefixes && prefixes->empty()) {
+            resv->remove("prefixes");
+        }
+        ConstElementPtr hostname = resv->get("hostname");
+        if (hostname && hostname->stringValue().empty()) {
+            resv->remove("hostname");
+        }
+        ConstElementPtr next_server = resv->get("next-server");
+        if (next_server && (next_server->stringValue() == "0.0.0.0")) {
+            resv->remove("next-server");
+        }
+        ConstElementPtr server_hostname = resv->get("server-hostname");
+        if (server_hostname && server_hostname->stringValue().empty()) {
+            resv->remove("server-hostname");
+        }
+        ConstElementPtr boot_file_name = resv->get("boot-file-name");
+        if (boot_file_name && boot_file_name->stringValue().empty()) {
+            resv->remove("boot-file-name");
+        }
+        ConstElementPtr client_classess = resv->get("client-classes");
+        if (client_classess && client_classess->empty()) {
+            resv->remove("client-classes");
+        }
+        ConstElementPtr option_data = resv->get("option-data");
+        if (option_data && option_data->empty()) {
+            resv->remove("option-data");
+        }
+    }
+    return (result);
+}
+
 // This test verifies that the parser can parse the reservation entry for
 // which hw-address is a host identifier.
 TEST_F(HostReservationParserTest, dhcp4HWaddr) {
@@ -299,6 +374,20 @@ TEST_F(HostReservationParserTest, dhcp4NoHostname) {
     EXPECT_EQ(0, hosts[0]->getIPv6SubnetID());
     EXPECT_EQ("192.0.2.10", hosts[0]->getIPv4Reservation().toText());
     EXPECT_TRUE(hosts[0]->getHostname().empty());
+
+    // lower duid value
+    boost::algorithm::to_lower(config);
+    CfgMgr::instance().setFamily(AF_INET);
+    CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+    runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);
+
+    CfgMgr::instance().setFamily(AF_INET6);
+    CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet6);
+
+    CfgMgr::instance().setFamily(AF_INET);
+    CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
 }
 
 // This test verifies that it is possible to specify DHCPv4 client classes
@@ -322,6 +411,18 @@ TEST_F(HostReservationParserTest, dhcp4ClientClasses) {
     ASSERT_EQ(2, classes.size());
     EXPECT_EQ(1, classes.count("foo"));
     EXPECT_EQ(1, classes.count("bar"));
+
+    CfgMgr::instance().setFamily(AF_INET);
+    CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+    runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);
+
+    CfgMgr::instance().setFamily(AF_INET6);
+    CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet6);
+
+    CfgMgr::instance().setFamily(AF_INET);
+    CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
 }
 
 // This test verifies that the parser can parse reservation entry
@@ -350,6 +451,24 @@ TEST_F(HostReservationParserTest, dhcp4MessageFields) {
     EXPECT_EQ("192.0.2.11", hosts[0]->getNextServer().toText());
     EXPECT_EQ("some-name.example.org", hosts[0]->getServerHostname());
     EXPECT_EQ("/tmp/some-file.efi", hosts[0]->getBootFileName());
+
+    // canonize hw-address
+    config_element->set("hw-address",
+                        Element::create(std::string("01:02:03:04:05:06")));
+    ElementPtr expected = Element::createList();
+    expected->add(config_element);
+
+    CfgMgr::instance().setFamily(AF_INET);
+    CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+    runToElementTest<CfgHostsSubnet>(expected, cfg_subnet);
+
+    CfgMgr::instance().setFamily(AF_INET6);
+    CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet6);
+
+    CfgMgr::instance().setFamily(AF_INET);
+    CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
 }
 
 // This test verifies that the invalid value of the next server is rejected.
@@ -435,6 +554,20 @@ TEST_F(HostReservationParserTest, noIPAddress) {
     EXPECT_EQ(0, hosts[0]->getIPv6SubnetID());
     EXPECT_EQ("0.0.0.0", hosts[0]->getIPv4Reservation().toText());
     EXPECT_EQ("foo.example.com", hosts[0]->getHostname());
+
+    // lower duid value
+    boost::algorithm::to_lower(config);
+    CfgMgr::instance().setFamily(AF_INET);
+    CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+    runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);
+
+    CfgMgr::instance().setFamily(AF_INET6);
+    CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet6);
+
+    CfgMgr::instance().setFamily(AF_INET);
+    CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
 }
 
 // This test verifies  that the configuration parser for host reservations
@@ -552,6 +685,24 @@ TEST_F(HostReservationParserTest, dhcp6HWaddr) {
                                             64),
                                   prefixes));
 
+    // canonize prefixes
+    config_element->set("prefixes",
+                        Element::fromJSON("[ \"2001:db8:2000:101::/64\", "
+                                          "\"2001:db8:2000:102::/64\" ]"));
+    ElementPtr expected = Element::createList();
+    expected->add(config_element);
+
+    CfgMgr::instance().setFamily(AF_INET6);
+    CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+    runToElementTest<CfgHostsSubnet>(expected, cfg_subnet);
+
+    CfgMgr::instance().setFamily(AF_INET);
+    CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(10));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);
+
+    CfgMgr::instance().setFamily(AF_INET6);
+    CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
 }
 
 // This test verifies that the parser can parse the IPv6 reservation entry for
@@ -590,6 +741,23 @@ TEST_F(HostReservationParserTest, dhcp6DUID) {
 
     IPv6ResrvRange prefixes = hosts[0]->getIPv6Reservations(IPv6Resrv::TYPE_PD);
     ASSERT_EQ(0, std::distance(prefixes.first, prefixes.second));
+
+    // remove prefixes and lower duid value
+    config_element->remove("prefixes");
+    config = prettyPrint(config_element);
+    boost::algorithm::to_lower(config);
+
+    CfgMgr::instance().setFamily(AF_INET6);
+    CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(12));
+    runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);
+
+    CfgMgr::instance().setFamily(AF_INET);
+    CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(12));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);
+
+    CfgMgr::instance().setFamily(AF_INET6);
+    CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
 }
 
 // This test verifies that host reservation parser for DHCPv6 rejects
@@ -649,6 +817,23 @@ TEST_F(HostReservationParserTest, dhcp6NoHostname) {
 
     IPv6ResrvRange prefixes = hosts[0]->getIPv6Reservations(IPv6Resrv::TYPE_PD);
     ASSERT_EQ(0, std::distance(prefixes.first, prefixes.second));
+
+    // remove prefixes and lower duid value
+    config_element->remove("prefixes");
+    config = prettyPrint(config_element);
+    boost::algorithm::to_lower(config);
+
+    CfgMgr::instance().setFamily(AF_INET6);
+    CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(12));
+    runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);
+
+    CfgMgr::instance().setFamily(AF_INET);
+    CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(12));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);
+
+    CfgMgr::instance().setFamily(AF_INET6);
+    CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(10));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
 }
 
 // This test verifies that it is possible to specify DHCPv4 client classes
@@ -673,6 +858,20 @@ TEST_F(HostReservationParserTest, dhcp6ClientClasses) {
     ASSERT_EQ(2, classes.size());
     EXPECT_EQ(1, classes.count("foo"));
     EXPECT_EQ(1, classes.count("bar"));
+
+    // lower duid value
+    boost::algorithm::to_lower(config);
+    CfgMgr::instance().setFamily(AF_INET6);
+    CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+    runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);
+
+    CfgMgr::instance().setFamily(AF_INET);
+    CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(10));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);
+
+    CfgMgr::instance().setFamily(AF_INET6);
+    CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
 }
 
 // This test verifies that the configuration parser throws an exception
@@ -800,6 +999,32 @@ TEST_F(HostReservationParserTest, options4) {
         OptionUint8>(retrieveOption(*hosts[0], DHCP4_OPTION_SPACE, DHO_DEFAULT_IP_TTL));
     ASSERT_TRUE(opt_ttl);
     EXPECT_EQ(64, opt_ttl->getValue());
+
+    // Canonize the config
+    ElementPtr option = config_element->get("option-data")->getNonConst(0);
+    option->set("code", Element::create(DHO_NAME_SERVERS));
+    option->set("space", Element::create(std::string(DHCP4_OPTION_SPACE)));
+    option->set("csv-format", Element::create(true));
+    option = config_element->get("option-data")->getNonConst(1);
+    option = config_element->get("option-data")->getNonConst(2);
+    option->set("code", Element::create(DHO_DEFAULT_IP_TTL));
+    option->set("space", Element::create(std::string(DHCP4_OPTION_SPACE)));
+    option->set("csv-format", Element::create(true));
+    ElementPtr expected = Element::createList();
+    expected->add(config_element);
+
+    // Try to unparse it.
+    CfgMgr::instance().setFamily(AF_INET);
+    CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+    runToElementTest<CfgHostsSubnet>(expected, cfg_subnet);
+
+    CfgMgr::instance().setFamily(AF_INET6);
+    CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet6);
+
+    CfgMgr::instance().setFamily(AF_INET);
+    CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
 }
 
 // This test verifies that it is possible to specify DHCPv6 options for
@@ -858,6 +1083,32 @@ TEST_F(HostReservationParserTest, options6) {
         OptionUint8>(retrieveOption(*hosts[0], DHCP6_OPTION_SPACE, D6O_PREFERENCE));
     ASSERT_TRUE(opt_prf);
     EXPECT_EQ(11, opt_prf->getValue());
+
+    // Canonize the config
+    ElementPtr option = config_element->get("option-data")->getNonConst(0);
+    option->set("code", Element::create(D6O_NAME_SERVERS));
+    option->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
+    option->set("csv-format", Element::create(true));
+    option = config_element->get("option-data")->getNonConst(1);
+    option = config_element->get("option-data")->getNonConst(2);
+    option->set("code", Element::create(D6O_PREFERENCE));
+    option->set("space", Element::create(std::string(DHCP6_OPTION_SPACE)));
+    option->set("csv-format", Element::create(true));
+    config = prettyPrint(config_element);
+    boost::algorithm::to_lower(config);
+    
+    // Try to unparse it.
+    CfgMgr::instance().setFamily(AF_INET6);
+    CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(10));
+    runToElementTest<CfgHostsSubnet>("[" + config + "]", cfg_subnet);
+
+    CfgMgr::instance().setFamily(AF_INET);
+    CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(10));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet4);
+
+    CfgMgr::instance().setFamily(AF_INET6);
+    CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
 }
 
 // This test verifies that it is possible to specify an empty list of
@@ -1008,6 +1259,8 @@ TEST_F(HostReservationIdsParserTest, dhcp4Identifiers) {
     EXPECT_EQ(*id++, Host::IDENT_DUID);
     EXPECT_EQ(*id++, Host::IDENT_HWADDR);
     EXPECT_EQ(*id++, Host::IDENT_CLIENT_ID);
+
+    runToElementTest<CfgHostOperations>(config, *cfg);
 }
 
 // Test that list of supported DHCPv6 identifiers list is correctly
@@ -1028,6 +1281,8 @@ TEST_F(HostReservationIdsParserTest, dhcp6Identifiers) {
     CfgHostOperations::IdentifierTypes::const_iterator id = ids.begin();
     EXPECT_EQ(*id++, Host::IDENT_DUID);
     EXPECT_EQ(*id++, Host::IDENT_HWADDR);
+
+    runToElementTest<CfgHostOperations>(config, *cfg);
 }
 
 // Test that invalid DHCPv4 identifier causes error.

+ 108 - 3
src/lib/dhcpsrv/tests/host_reservations_list_parser_unittest.cc

@@ -11,11 +11,14 @@
 #include <dhcp/hwaddr.h>
 #include <dhcpsrv/cfgmgr.h>
 #include <dhcpsrv/cfg_hosts.h>
+#include <dhcpsrv/cfg_hosts_util.h>
 #include <dhcpsrv/host.h>
 #include <dhcpsrv/subnet_id.h>
 #include <dhcpsrv/parsers/dhcp_parsers.h>
 #include <dhcpsrv/parsers/host_reservation_parser.h>
 #include <dhcpsrv/parsers/host_reservations_list_parser.h>
+#include <testutils/test_to_element.h>
+#include <boost/algorithm/string.hpp>
 #include <gtest/gtest.h>
 #include <sstream>
 #include <string>
@@ -23,6 +26,7 @@
 
 using namespace isc::data;
 using namespace isc::dhcp;
+using namespace isc::test;
 
 namespace {
 
@@ -67,9 +71,82 @@ HostReservationsListParserTest::TearDown() {
     CfgMgr::instance().clear();
 }
 
+/// @brief class of subnet_id reservations
+class CfgHostsSubnet : public CfgToElement {
+public:
+    /// @brief constructor
+    CfgHostsSubnet(ConstCfgHostsPtr hosts, SubnetID id)
+        : hosts_(hosts), id_(id) { }
+
+    /// @brief unparse method
+    ElementPtr toElement() const;
+
+private:
+    /// @brief the host reservation configuration
+    ConstCfgHostsPtr hosts_;
+
+    /// @brief the subnet ID
+    SubnetID id_;
+};
+
+ElementPtr
+CfgHostsSubnet::toElement() const {
+    CfgHostsList list;
+    try {
+        list.internalize(hosts_->toElement());
+    } catch (const std::exception& ex) {
+        ADD_FAILURE() << "CfgHostsSubnet::toElement: " << ex.what();
+    }
+    ElementPtr result = boost::const_pointer_cast<Element>(list.get(id_));
+
+    // Strip
+    for (size_t i = 0; i < result->size(); ++i) {
+        ElementPtr resv = result->getNonConst(i);
+        ConstElementPtr ip_address = resv->get("ip-address");
+        if (ip_address && (ip_address->stringValue() == "0.0.0.0")) {
+            resv->remove("ip-address");
+        }
+        ConstElementPtr ip_addresses = resv->get("ip-addresses");
+        if (ip_addresses && ip_addresses->empty()) {
+            resv->remove("ip-addresses");
+        }
+        ConstElementPtr prefixes = resv->get("prefixes");
+        if (prefixes && prefixes->empty()) {
+            resv->remove("prefixes");
+        }
+        ConstElementPtr hostname = resv->get("hostname");
+        if (hostname && hostname->stringValue().empty()) {
+            resv->remove("hostname");
+        }
+        ConstElementPtr next_server = resv->get("next-server");
+        if (next_server && (next_server->stringValue() == "0.0.0.0")) {
+            resv->remove("next-server");
+        }
+        ConstElementPtr server_hostname = resv->get("server-hostname");
+        if (server_hostname && server_hostname->stringValue().empty()) {
+            resv->remove("server-hostname");
+        }
+        ConstElementPtr boot_file_name = resv->get("boot-file-name");
+        if (boot_file_name && boot_file_name->stringValue().empty()) {
+            resv->remove("boot-file-name");
+        }
+        ConstElementPtr client_classess = resv->get("client-classes");
+        if (client_classess && client_classess->empty()) {
+            resv->remove("client-classes");
+        }
+        ConstElementPtr option_data = resv->get("option-data");
+        if (option_data && option_data->empty()) {
+            resv->remove("option-data");
+        }
+    }
+    return (result);
+}
+
 // This test verifies that the parser for the list of the host reservations
 // parses IPv4 reservations correctly.
 TEST_F(HostReservationsListParserTest, ipv4Reservations) {
+    CfgMgr::instance().setFamily(AF_INET);
+    // hexadecimal in lower case for toElement()
     std::string config =
         "[ "
         "  { "
@@ -109,6 +186,19 @@ TEST_F(HostReservationsListParserTest, ipv4Reservations) {
     EXPECT_EQ(0, hosts[0]->getIPv6SubnetID());
     EXPECT_EQ("192.0.2.110", hosts[0]->getIPv4Reservation().toText());
     EXPECT_EQ("bar.example.com", hosts[0]->getHostname());
+
+    // Get back the config from cfg_hosts
+    boost::algorithm::to_lower(config);
+    CfgHostsSubnet cfg_subnet(cfg_hosts, SubnetID(1));
+    runToElementTest<CfgHostsSubnet>(config, cfg_subnet);
+
+    CfgMgr::instance().setFamily(AF_INET6);
+    CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(1));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet6);
+
+    CfgMgr::instance().setFamily(AF_INET);
+    CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(0));
+    runToElementTest<CfgHostsSubnet>("[ ]", cfg_subnet1);
 }
 
 // This test verifies that an attempt to add two reservations with the
@@ -147,6 +237,7 @@ TEST_F(HostReservationsListParserTest, duplicatedIdentifierValue4) {
 // This test verifies that the parser for the list of the host reservations
 // parses IPv6 reservations correctly.
 TEST_F(HostReservationsListParserTest, ipv6Reservations) {
+    // hexadecimal in lower case for toElement()
     std::string config = 
         "[ "
         "  { \"duid\": \"01:02:03:04:05:06:07:08:09:0A\","
@@ -156,7 +247,6 @@ TEST_F(HostReservationsListParserTest, ipv6Reservations) {
         "  }, "
         "  { \"hw-address\": \"01:02:03:04:05:06\","
         "    \"ip-addresses\": [ \"2001:db8:1::123\" ],"
-        "    \"prefixes\": [ ],"
         "    \"hostname\": \"bar.example.com\" "
         "  } "
         "]";
@@ -202,6 +292,23 @@ TEST_F(HostReservationsListParserTest, ipv6Reservations) {
     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());
+
+    // 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
@@ -236,6 +343,4 @@ TEST_F(HostReservationsListParserTest, duplicatedIdentifierValue6) {
     }
 }
 
-
-
 } // end of anonymous namespace

+ 0 - 2
src/lib/dhcpsrv/tests/srv_config_unittest.cc

@@ -443,8 +443,6 @@ TEST_F(SrvConfigTest, unparse) {
     defaults += "\"expired-leases-processing\": ";
     defaults += conf.getCfgExpiration()->toElement()->str() + ",\n";
     defaults += "\"lease-database\": { \"type\": \"memfile\" },\n";
-    defaults += "\"hosts-database\": { },\n";
-    defaults += "\"client-classes\": [ ],\n";
     defaults += "\"hooks-libraries\": [ ],\n";
     defaults += "\"dhcp-ddns\": \n";
     defaults += conf.getD2ClientConfig()->toElement()->str() + ",\n";