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
#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>
/// 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));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
int cnt = 0; // Number of reconfigurations
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
int cnt = 0; // Number of reconfigurations
do {
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
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());
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
ConstElementPtr status;
ASSERT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
ConstElementPtr status;
ASSERT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
"\"valid-lifetime\": 4000 }";
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
ConstElementPtr status;
ASSERT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
"}";
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()->
"}";
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()->
"}";
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()->
"}";
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()->
"}";
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()->
"}";
ConstElementPtr json;
ASSERT_NO_THROW(json = parseOPTION_DEF(config));
+ extractConfig(config);
OptionDefinitionPtr def = CfgMgr::instance().getStagingCfg()->
getCfgOptionDef()->get(DHCP4_OPTION_SPACE, 109);
" } ]"
"}";
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));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
checkResult(x, 0);
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
checkResult(x, 0);
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
"}";
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ASSERT_TRUE(status);
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
checkResult(x, 0);
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
checkResult(x, 0);
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ASSERT_TRUE(status);
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
ConstElementPtr status;
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
ConstElementPtr status;
// 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));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
checkResult(x, 0);
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config, true));
+ extractConfig(config);
EXPECT_NO_THROW(x = configureDhcp4Server(*srv_, json));
checkResult(x, 0);
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(hr_config));
+ extractConfig(hr_config);
ConstElementPtr result;
EXPECT_NO_THROW(result = configureDhcp4Server(*srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
"}";
ConstElementPtr config;
ASSERT_NO_THROW(config = parseDHCP4(config_txt));
+ extractConfig(config_txt);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, config));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP4(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp4Server(*srv_, json));
// 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);
// 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);
// 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);
// 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);
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();
--- /dev/null
+// 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));
+
+};
--- /dev/null
+// 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));
+
+};
--- /dev/null
+// 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
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
#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>
/// 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);
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
do {
EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
do {
EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config4));
+ extractConfig(config4);
EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
checkResult(x, 0);
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
// 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));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
"\"valid-lifetime\": 4000 }";
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
ConstElementPtr status;
ASSERT_NO_THROW(status = configureDhcp6Server(srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
// 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.
// 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.
// 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.
// 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.
"}";
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()->
"}";
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()->
"}";
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()->
"}";
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()->
"}";
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()->
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
checkResult(x, 0);
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
checkResult(x, 0);
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
"}";
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
ASSERT_TRUE(status);
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
checkResult(x, 0);
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
checkResult(x, 0);
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
ASSERT_TRUE(status);
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
"}";
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
ASSERT_TRUE(status);
checkResult(status, 0);
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
checkResult(x, 0);
// 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;
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
checkResult(x, 0);
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
EXPECT_NO_THROW(x = configureDhcp6Server(srv_, json));
checkResult(x, 0);
/// 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));
/// 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));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(HR_CONFIG));
+ extractConfig(HR_CONFIG);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
/// 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));
"}";
ConstElementPtr config;
ASSERT_NO_THROW(config = parseDHCP6(config_txt));
+ extractConfig(config_txt);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, config));
"}";
ConstElementPtr config;
ASSERT_NO_THROW(config = parseDHCP6(config_txt));
+ extractConfig(config_txt);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, config));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
ConstElementPtr json;
ASSERT_NO_THROW(json = parseDHCP6(config));
+ extractConfig(config);
ConstElementPtr status;
EXPECT_NO_THROW(status = configureDhcp6Server(srv_, json));
// 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);
// 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);
// 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);
// 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);
// 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);
// 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);
// 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);
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();
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
-// 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
#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 {
}
}
+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
-// 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
#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>
/// 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.
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.
/// - 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.
--- /dev/null
+// 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());
+ }
+}
+
+}
+}
--- /dev/null
+// 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
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);
}
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) {
#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_*
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)
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) {
}
// 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);
-// 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
#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>
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);
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),
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",
}
// 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.
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",
}
}
+// 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) {
}
}
+// 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) {
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,
" \"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";
void clear() {
CfgMgr::instance().setVerbose(false);
+ CfgMgr::instance().setFamily(AF_INET);
CfgMgr::instance().clear();
LeaseMgrFactory::destroy();
}
#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>
using namespace isc::asiolink;
using namespace isc::data;
using namespace isc::dhcp;
+using namespace isc::test;
namespace {
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) {
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
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
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.
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
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
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
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
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
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
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
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
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.
#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>
using namespace isc::data;
using namespace isc::dhcp;
+using namespace isc::test;
namespace {
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 =
"[ "
" { "
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
// 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\","
" }, "
" { \"hw-address\": \"01:02:03:04:05:06\","
" \"ip-addresses\": [ \"2001:db8:1::123\" ],"
- " \"prefixes\": [ ],"
" \"hostname\": \"bar.example.com\" "
" } "
"]";
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
}
}
-
-
} // end of anonymous namespace
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";