diff --git a/src/bin/dhcp4/tests/Makefile.am b/src/bin/dhcp4/tests/Makefile.am index 68c923eaafa009fbe02ad35223ae03122855a94f..63955a0ab93b3c605d96b07ba5dd9098545e29c0 100644 --- a/src/bin/dhcp4/tests/Makefile.am +++ b/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 diff --git a/src/bin/dhcp4/tests/config_parser_unittest.cc b/src/bin/dhcp4/tests/config_parser_unittest.cc index 3f281b6770622ad5793d996905e57af391def411..f07934f8f9585501df4e893b205c7ba0b82357dc 100644 --- a/src/bin/dhcp4/tests/config_parser_unittest.cc +++ b/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 #include @@ -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); diff --git a/src/bin/dhcp4/tests/dhcp4_test_utils.cc b/src/bin/dhcp4/tests/dhcp4_test_utils.cc index 7924125962aa76d14177b612c88a08992bfcc117..ca17ad2b3c4ee2d84be642fac118ebe641430554 100644 --- a/src/bin/dhcp4/tests/dhcp4_test_utils.cc +++ b/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(); diff --git a/src/bin/dhcp4/tests/get_config_unittest.cc b/src/bin/dhcp4/tests/get_config_unittest.cc new file mode 100644 index 0000000000000000000000000000000000000000..59ac527a1df8c3b575bafb799cb5ca8fa4de427a --- /dev/null +++ b/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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +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 lines; + boost::split(lines, prettier, boost::is_any_of("\n")); + // add escapes using again JSON + std::list 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 { +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 .". + /// + /// @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 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)); + +}; diff --git a/src/bin/dhcp4/tests/get_config_unittest.cc.skel b/src/bin/dhcp4/tests/get_config_unittest.cc.skel new file mode 100644 index 0000000000000000000000000000000000000000..59ac527a1df8c3b575bafb799cb5ca8fa4de427a --- /dev/null +++ b/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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +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 lines; + boost::split(lines, prettier, boost::is_any_of("\n")); + // add escapes using again JSON + std::list 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 { +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 .". + /// + /// @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 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)); + +}; diff --git a/src/bin/dhcp4/tests/get_config_unittest.h b/src/bin/dhcp4/tests/get_config_unittest.h new file mode 100644 index 0000000000000000000000000000000000000000..e1718e0b949fb564e4c43a8de09db43b6b6770a9 --- /dev/null +++ b/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 + +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 diff --git a/src/bin/dhcp6/tests/Makefile.am b/src/bin/dhcp6/tests/Makefile.am index 2d844b78e4ac7b5719908ad17488ca53ff4c9cb4..d13b2a3792596c8c5ee057ccf744713cd2c8d8d4 100644 --- a/src/bin/dhcp6/tests/Makefile.am +++ b/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 diff --git a/src/bin/dhcp6/tests/config_parser_unittest.cc b/src/bin/dhcp6/tests/config_parser_unittest.cc index 1d4ec0bfe256c7799946e5ded7c2b97d2f9f7a84..71273d0bcf3336136e7e067200df8cc627409888 100644 --- a/src/bin/dhcp6/tests/config_parser_unittest.cc +++ b/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 #include @@ -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); diff --git a/src/bin/dhcp6/tests/dhcp6_test_utils.cc b/src/bin/dhcp6/tests/dhcp6_test_utils.cc index 94a74e71b1e7ac85ecdc5e2c2f9443e83a3e6f9a..3aa93f599a8377f816ee3a901203eabd26d4eb41 100644 --- a/src/bin/dhcp6/tests/dhcp6_test_utils.cc +++ b/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(); diff --git a/src/lib/dhcpsrv/Makefile.am b/src/lib/dhcpsrv/Makefile.am index 004e3eb76f859c3be39cecf2425b412d2be219ad..9a0190cc24c0cdd94184fd98a40f9406da56539c 100644 --- a/src/lib/dhcpsrv/Makefile.am +++ b/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 diff --git a/src/lib/dhcpsrv/cfg_hosts.cc b/src/lib/dhcpsrv/cfg_hosts.cc index bc0c1167d255f5a1413d367b398db5bf5e741ae5..a5b1933b003f18e39fe07725b39a4865fe72958f 100644 --- a/src/lib/dhcpsrv/cfg_hosts.cc +++ b/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 #include +#include #include +#include #include +#include #include +#include +#include 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& 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& 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 diff --git a/src/lib/dhcpsrv/cfg_hosts.h b/src/lib/dhcpsrv/cfg_hosts.h index c53a7d5a3298ec5284317e12b44d6f61bd045e6e..1e74bc689eefdb2596b765192274fae4920ecce8 100644 --- a/src/lib/dhcpsrv/cfg_hosts.h +++ b/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 +#include #include #include #include @@ -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": [ , ] }, + /// { "id": 456, "reservations": [ +#include +#include +#include +#include + +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 " <(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(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(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()); + } +} + +} +} diff --git a/src/lib/dhcpsrv/cfg_hosts_util.h b/src/lib/dhcpsrv/cfg_hosts_util.h new file mode 100644 index 0000000000000000000000000000000000000000..67803b999069d7489aa42fb5d7d9e8ea64006f62 --- /dev/null +++ b/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 +#include + +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 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 diff --git a/src/lib/dhcpsrv/cfg_subnets4.cc b/src/lib/dhcpsrv/cfg_subnets4.cc index 55c205dcb5d7fcdf7800b4119b6606da6a78bbed..e00ce1344505a0051461d5dbf4b1fa9590a81796 100644 --- a/src/lib/dhcpsrv/cfg_subnets4.cc +++ b/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) { diff --git a/src/lib/dhcpsrv/srv_config.cc b/src/lib/dhcpsrv/srv_config.cc index 13949c7da7607f96fc4d80f7a4761977ff956563..8cf0291a273bdcbb77f7d75b38610f78228d5cf3 100644 --- a/src/lib/dhcpsrv/srv_config.cc +++ b/src/lib/dhcpsrv/srv_config.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include // 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& sn_list = subnets->listValue(); + for (std::vector::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); diff --git a/src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc b/src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc index f8529d34b6b4ce87670d9abe689859d65491e911..73dc6a4e53f5f85f0b42c2f5b05500bc3f10c2ca 100644 --- a/src/lib/dhcpsrv/tests/cfg_hosts_unittest.cc +++ b/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 #include #include +#include #include +#include #include #include #include @@ -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, diff --git a/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc b/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc index ab39f4426479ad2e036fcbb4a764695c4b3ca2e5..dfec17744e84b551c22e7ee1571c37d80c9161f0 100644 --- a/src/lib/dhcpsrv/tests/cfg_subnets4_unittest.cc +++ b/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"; diff --git a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc b/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc index 8c1bfbd56081906c20511a168778949e35cdf542..e35e7ad7839059f49a955c0e17b149f9b482db5d 100644 --- a/src/lib/dhcpsrv/tests/cfgmgr_unittest.cc +++ b/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(); } diff --git a/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc b/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc index b7b683736d53c9efe325ef5fe0cbe5392480eeb4..4fa43342f057768213f4c908dec8ae766337b5a1 100644 --- a/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc +++ b/src/lib/dhcpsrv/tests/host_reservation_parser_unittest.cc @@ -14,11 +14,14 @@ #include #include #include +#include #include #include #include #include +#include #include +#include #include #include #include @@ -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(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("[" + config + "]", cfg_subnet); + + CfgMgr::instance().setFamily(AF_INET6); + CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10)); + runToElementTest("[ ]", cfg_subnet6); + + CfgMgr::instance().setFamily(AF_INET); + CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1)); + runToElementTest("[ ]", 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("[" + config + "]", cfg_subnet); + + CfgMgr::instance().setFamily(AF_INET6); + CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10)); + runToElementTest("[ ]", cfg_subnet6); + + CfgMgr::instance().setFamily(AF_INET); + CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1)); + runToElementTest("[ ]", 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(expected, cfg_subnet); + + CfgMgr::instance().setFamily(AF_INET6); + CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10)); + runToElementTest("[ ]", cfg_subnet6); + + CfgMgr::instance().setFamily(AF_INET); + CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1)); + runToElementTest("[ ]", 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("[" + config + "]", cfg_subnet); + + CfgMgr::instance().setFamily(AF_INET6); + CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10)); + runToElementTest("[ ]", cfg_subnet6); + + CfgMgr::instance().setFamily(AF_INET); + CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1)); + runToElementTest("[ ]", 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(expected, cfg_subnet); + + CfgMgr::instance().setFamily(AF_INET); + CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(10)); + runToElementTest("[ ]", cfg_subnet4); + + CfgMgr::instance().setFamily(AF_INET6); + CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1)); + runToElementTest("[ ]", 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("[" + config + "]", cfg_subnet); + + CfgMgr::instance().setFamily(AF_INET); + CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(12)); + runToElementTest("[ ]", cfg_subnet4); + + CfgMgr::instance().setFamily(AF_INET6); + CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1)); + runToElementTest("[ ]", 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("[" + config + "]", cfg_subnet); + + CfgMgr::instance().setFamily(AF_INET); + CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(12)); + runToElementTest("[ ]", cfg_subnet4); + + CfgMgr::instance().setFamily(AF_INET6); + CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(10)); + runToElementTest("[ ]", 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("[" + config + "]", cfg_subnet); + + CfgMgr::instance().setFamily(AF_INET); + CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(10)); + runToElementTest("[ ]", cfg_subnet4); + + CfgMgr::instance().setFamily(AF_INET6); + CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1)); + runToElementTest("[ ]", 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(expected, cfg_subnet); + + CfgMgr::instance().setFamily(AF_INET6); + CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(10)); + runToElementTest("[ ]", cfg_subnet6); + + CfgMgr::instance().setFamily(AF_INET); + CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1)); + runToElementTest("[ ]", 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("[" + config + "]", cfg_subnet); + + CfgMgr::instance().setFamily(AF_INET); + CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(10)); + runToElementTest("[ ]", cfg_subnet4); + + CfgMgr::instance().setFamily(AF_INET6); + CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1)); + runToElementTest("[ ]", 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(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(config, *cfg); } // Test that invalid DHCPv4 identifier causes error. diff --git a/src/lib/dhcpsrv/tests/host_reservations_list_parser_unittest.cc b/src/lib/dhcpsrv/tests/host_reservations_list_parser_unittest.cc index 9c12d9174c922cd35fcae17c1722d456dd56fe83..1d0ec65604193931a92a447d0707bad96df0fee3 100644 --- a/src/lib/dhcpsrv/tests/host_reservations_list_parser_unittest.cc +++ b/src/lib/dhcpsrv/tests/host_reservations_list_parser_unittest.cc @@ -11,11 +11,14 @@ #include #include #include +#include #include #include #include #include #include +#include +#include #include #include #include @@ -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(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(config, cfg_subnet); + + CfgMgr::instance().setFamily(AF_INET6); + CfgHostsSubnet cfg_subnet6(cfg_hosts, SubnetID(1)); + runToElementTest("[ ]", cfg_subnet6); + + CfgMgr::instance().setFamily(AF_INET); + CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(0)); + runToElementTest("[ ]", 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(config, cfg_subnet); + + CfgMgr::instance().setFamily(AF_INET); + CfgHostsSubnet cfg_subnet4(cfg_hosts, SubnetID(2)); + runToElementTest("[ ]", cfg_subnet4); + + CfgMgr::instance().setFamily(AF_INET6); + CfgHostsSubnet cfg_subnet1(cfg_hosts, SubnetID(1)); + runToElementTest("[ ]", 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 diff --git a/src/lib/dhcpsrv/tests/srv_config_unittest.cc b/src/lib/dhcpsrv/tests/srv_config_unittest.cc index c2ab73ecc9b973b365ec44c7740353827cb68d3b..8b68a0c5853eaf7815f9ee440ae880d86295c393 100644 --- a/src/lib/dhcpsrv/tests/srv_config_unittest.cc +++ b/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";