From: Francis Dupont Date: Sun, 7 Oct 2018 21:56:59 +0000 (+0200) Subject: [65-libyang-adaptors] Added high level adaptors from lib-yang X-Git-Tag: 65-libyang-config-adaptor_base~9 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=c929ddb9f89241a33b2248e2989dccf2691bfac7;p=thirdparty%2Fkea.git [65-libyang-adaptors] Added high level adaptors from lib-yang --- diff --git a/src/lib/yang/Makefile.am b/src/lib/yang/Makefile.am index a0f0de8dbe..bfba24e36a 100644 --- a/src/lib/yang/Makefile.am +++ b/src/lib/yang/Makefile.am @@ -6,6 +6,10 @@ AM_CXXFLAGS = $(KEA_CXXFLAGS) lib_LTLIBRARIES = libkea-yang.la libkea_yang_la_SOURCES = adaptor.cc adaptor.h +libkea_yang_la_SOURCES += adaptor_host.cc adaptor_host.h +libkea_yang_la_SOURCES += adaptor_pool.cc adaptor_pool.h +libkea_yang_la_SOURCES += adaptor_option.cc adaptor_option.h +libkea_yang_la_SOURCES += adaptor_subnet.cc adaptor_subnet.h libkea_yang_la_SOURCES += sysrepo_error.h libkea_yang_la_SOURCES += translator.cc translator.h libkea_yang_la_SOURCES += translator_control_socket.cc @@ -36,6 +40,10 @@ libkea_yang_la_LDFLAGS = -no-undefined -version-info 0:0:0 libkea_yang_includedir = $(pkgincludedir)/yang libkea_yang_include_HEADERS = \ adaptor.h \ + adaptor_host.h \ + adaptor_option.h \ + adaptor_pool.h \ + adaptor_subnet.h \ sysrepo_error.h \ translator.h \ translator_control_socket.h \ diff --git a/src/lib/yang/adaptor_host.cc b/src/lib/yang/adaptor_host.cc new file mode 100644 index 0000000000..0a7ed9ec08 --- /dev/null +++ b/src/lib/yang/adaptor_host.cc @@ -0,0 +1,65 @@ +// Copyright (C) 2018 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 + +using namespace std; +using namespace isc::data; +using namespace isc::util; + +namespace isc { +namespace yang { + +const string +AdaptorHost::STD_CHARACTERS = + "0123456789@ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-.@_"; + +AdaptorHost::AdaptorHost() { +} + +AdaptorHost::~AdaptorHost() { +} + +void +AdaptorHost::quoteIdentifier(ElementPtr host) { + ConstElementPtr flex_id = host->get("flex-id"); + if (!flex_id) { + return; + } + const string& id = flex_id->stringValue(); + if (id.empty()) { + isc_throw(BadValue, "empty flexible identifier in " << host->str()); + } + // No special and no not printable characters? + if (id.find_first_not_of(STD_CHARACTERS) == string::npos) { + return; + } + // Quoted identifier? + vector binary = str::quotedStringToBinary(id); + if (binary.empty()) { + binary.assign(id.begin(), id.end()); + } + // Convert in hexadecimal (from DUID::toText()). + stringstream tmp; + tmp << hex; + bool delim = false; + for (vector::const_iterator it = binary.begin(); + it != binary.end(); ++it) { + if (delim) { + tmp << ":"; + } + tmp << setw(2) << setfill('0') << static_cast(*it); + delim = true; + } + host->set("flex-id", Element::create(tmp.str())); +} + +}; // end of namespace isc::yang +}; // end of namespace isc diff --git a/src/lib/yang/adaptor_host.h b/src/lib/yang/adaptor_host.h new file mode 100644 index 0000000000..4c12098d2b --- /dev/null +++ b/src/lib/yang/adaptor_host.h @@ -0,0 +1,47 @@ +// Copyright (C) 2018 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 ISC_ADAPTOR_HOST_H +#define ISC_ADAPTOR_HOST_H 1 + +#include + +namespace isc { +namespace yang { + +/// @brief JSON adaptor for host reservations quoting identifiers. +/// +/// The identifier type and identifier value are used as keys in YANG +/// host reservation lists so soem constraints were put on their contents. +/// For instance a quoted flex-id identifier raises an error (keys +/// are between quotes in setItem commands). +class AdaptorHost { +public: + + /// @brief The string of standard (vs special or not printable) + /// characters (digit, letters, -, ., @, _). + static const std::string STD_CHARACTERS; + + /// @brief Constructor. + AdaptorHost(); + + /// @brief Destructor. + virtual ~AdaptorHost(); + + /// @brief Quote when needed a host identifier. + /// + /// Check if the flex-id identifier includes a special (including quote) + /// or not printable character. When it is the case produce and replace + /// by a hexadecimal identifier trying first for a quoted identifier. + /// + /// @param host The host. + static void quoteIdentifier(isc::data::ElementPtr host); +}; + +}; // end of namespace isc::yang +}; // end of namespace isc + +#endif // ISC_ADAPTOR_HOST_H diff --git a/src/lib/yang/adaptor_option.cc b/src/lib/yang/adaptor_option.cc new file mode 100644 index 0000000000..901fccb9e8 --- /dev/null +++ b/src/lib/yang/adaptor_option.cc @@ -0,0 +1,124 @@ +// Copyright (C) 2018 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 + +using namespace std; +using namespace isc::data; +using namespace isc::dhcp; + +namespace isc { +namespace yang { + +AdaptorOption::AdaptorOption() { +} + +AdaptorOption::~AdaptorOption() { +} + +void +AdaptorOption::setSpace(ElementPtr option, const string& space) { + if (!option->contains("space")) { + option->set("space", Element::create(space)); + } +} + +void +AdaptorOption::checkType(ConstElementPtr option) { + if (!option->contains("type")) { + isc_throw(MissingKey, "missing type in option definition " + << option->str()); + } +} + +void +AdaptorOption::checkCode(ConstElementPtr option) { + if (!option->contains("code")) { + isc_throw(MissingKey, "missing code in option " << option->str()); + } +} + +void +AdaptorOption::collect(ConstElementPtr option, OptionCodes& codes) { + ConstElementPtr space = option->get("space"); + ConstElementPtr code = option->get("code"); + ConstElementPtr name = option->get("name"); + if (name) { + string index = space->stringValue() + "@" + name->stringValue(); + uint16_t val = static_cast(code->intValue()); + codes.insert(std::pair(index, val)); + } +} + +void +AdaptorOption::setCode(ElementPtr option, const OptionCodes& codes) { + ConstElementPtr code = option->get("code"); + if (!code) { + ConstElementPtr name = option->get("name"); + if (!name) { + isc_throw(MissingKey, "missing name and code in option " + << option->str()); + } + ConstElementPtr space = option->get("space"); + string index = space->stringValue() + "@" + name->stringValue(); + OptionCodes::const_iterator it = codes.find(index); + if (it == codes.end()) { + isc_throw(MissingKey, "can't get code from option " + << option->str()); + } + option->set("code", Element::create(static_cast(it->second))); + } +} + +void +AdaptorOption::initCodes(OptionCodes& codes, const string& space) { + if (space == "dhcp4") { + initCodesInternal(codes, space, STANDARD_V4_OPTION_DEFINITIONS, + STANDARD_V4_OPTION_DEFINITIONS_SIZE); + initCodesInternal(codes, space, LAST_RESORT_V4_OPTION_DEFINITIONS, + LAST_RESORT_V4_OPTION_DEFINITIONS_SIZE); + initCodesInternal(codes, "vendor-4491", + DOCSIS3_V4_DEFS, DOCSIS3_V4_DEFS_SIZE); + } else if (space == "dhcp6") { + initCodesInternal(codes, space, STANDARD_V6_OPTION_DEFINITIONS, + STANDARD_V6_OPTION_DEFINITIONS_SIZE); + initCodesInternal(codes, "vendor-4491", + DOCSIS3_V6_DEFS, DOCSIS3_V6_DEFS_SIZE); + initCodesInternal(codes, MAPE_V6_OPTION_SPACE, + MAPE_V6_OPTION_DEFINITIONS, + MAPE_V6_OPTION_DEFINITIONS_SIZE); + initCodesInternal(codes, MAPT_V6_OPTION_SPACE, + MAPT_V6_OPTION_DEFINITIONS, + MAPT_V6_OPTION_DEFINITIONS_SIZE); + initCodesInternal(codes, LW_V6_OPTION_SPACE, + LW_V6_OPTION_DEFINITIONS, + LW_V6_OPTION_DEFINITIONS_SIZE); + initCodesInternal(codes, V4V6_RULE_OPTION_SPACE, + V4V6_RULE_OPTION_DEFINITIONS, + V4V6_RULE_OPTION_DEFINITIONS_SIZE); + initCodesInternal(codes, V4V6_BIND_OPTION_SPACE, + V4V6_BIND_OPTION_DEFINITIONS, + V4V6_BIND_OPTION_DEFINITIONS_SIZE); + initCodesInternal(codes, "vendor-2495", + ISC_V6_OPTION_DEFINITIONS, + ISC_V6_OPTION_DEFINITIONS_SIZE); + } +} + +void +AdaptorOption::initCodesInternal(OptionCodes& codes, const string& space, + const OptionDefParams* params, + size_t params_size) { + for (size_t i = 0; i < params_size; ++i) { + string index = space + "@" + params[i].name; + codes.insert(std::pair(index, params[i].code)); + } +} + +}; // end of namespace isc::yang +}; // end of namespace isc diff --git a/src/lib/yang/adaptor_option.h b/src/lib/yang/adaptor_option.h new file mode 100644 index 0000000000..69a3ce86e8 --- /dev/null +++ b/src/lib/yang/adaptor_option.h @@ -0,0 +1,104 @@ +// Copyright (C) 2018 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 ISC_ADAPTOR_OPTION_H +#define ISC_ADAPTOR_OPTION_H 1 + +#include +#include +#include + +namespace isc { + +namespace dhcp { +/// @brief Forward declaration of option definition parameters. +struct OptionDefParams; +}; + +namespace yang { + +/// @brief Map for DHCP option definitions handling code and +/// an index built from space and name. +typedef std::map OptionCodes; + +/// @brief JSON adaptor for option data or definition setting defaults. +/// +/// Both option data and option definition lists are keyed by the code +/// and space pair so both must be available in setOptionXXX methods. +/// For space there is an implicit default so setSpace must be called +/// to add this default to option entries without space. +/// Note remaining adaptors assume this was done first. +/// +/// checkCode and checkType can be used to check if code or type is present +/// (type is mandatory in option definitions). +/// +/// A missing code can be found in standard definitions (loaded by initCodes) +/// or in configuration option definitions (at global and client classes +/// scopes). setCode uses the space+name to code map to set missing codes +/// and raises an error when it can't. +class AdaptorOption { +public: + + /// @brief Constructor. + AdaptorOption(); + + /// @brief Destructor. + virtual ~AdaptorOption(); + + /// @brief Set space. + /// + /// @param option The option. + /// @param space The default space name. + static void setSpace(isc::data::ElementPtr option, + const std::string& space); + + /// @brief Check type (option definition). + /// + /// @param option The option. + /// @throw MissingKey if the type is not present. + static void checkType(isc::data::ConstElementPtr option); + + /// @brief Check code. + /// + /// @param option The option. + /// @throw MissingKey if the code is not present. + static void checkCode(isc::data::ConstElementPtr option); + + /// @brief Collect definition. + /// + /// @param option The option definition. + /// @param codes The reference to option definitions. + static void collect(isc::data::ConstElementPtr option, OptionCodes& codes); + + /// @brief Set code from name and definitions. + /// + /// @param option The option data. + /// @param codes Option definitions. + static void setCode(isc::data::ElementPtr option, + const OptionCodes& codes); + + /// @brief Initialize code map. + /// + /// @param codes The reference to option definitions. + /// @param space The space name. + static void initCodes(OptionCodes& codes, const std::string& space); + +protected: + /// @brief Initialize code map from option definition parameters. + /// + /// @param codes The reference to option definitions. + /// @param space The space name. + /// @param params Array of option definition parameters + /// @param params_size The size of the array. + static void initCodesInternal(OptionCodes& codes, const std::string& space, + const isc::dhcp::OptionDefParams* params, + size_t params_size); +}; + +}; // end of namespace isc::yang +}; // end of namespace isc + +#endif // ISC_ADAPTOR_OPTION_H diff --git a/src/lib/yang/adaptor_pool.cc b/src/lib/yang/adaptor_pool.cc new file mode 100644 index 0000000000..56dbd07ccc --- /dev/null +++ b/src/lib/yang/adaptor_pool.cc @@ -0,0 +1,85 @@ +// Copyright (C) 2018 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 + +using namespace std; +using namespace isc::data; + +namespace isc { +namespace yang { + +AdaptorPool::AdaptorPool() { +} + +AdaptorPool::~AdaptorPool() { +} + +void +AdaptorPool::canonizePool(ElementPtr pool) { + const string& orig = pool->get("pool")->stringValue(); + vector v; + for (char ch : orig) { + if ((ch == ' ') || (ch == '\t') || (ch == '\n')) { + continue; + } else if (ch == '-') { + v.push_back(' '); + v.push_back(ch); + v.push_back(' '); + } else { + v.push_back(ch); + } + } + string canon; + canon.assign(v.begin(), v.end()); + if (orig != canon) { + pool->set("pool", Element::create(canon)); + } +} + +void +AdaptorPool::fromSubnet(const string& model, ConstElementPtr subnet, + ConstElementPtr pools) { + if (model == IETF_DHCPV6_SERVER) { + fromSubnetIetf6(subnet, pools); + } else if ((model != KEA_DHCP4_SERVER) && + (model != KEA_DHCP6_SERVER)) { + isc_throw(NotImplemented, + "fromSubnet not implemented for the model: " << model); + } +} + +void +AdaptorPool::fromSubnetIetf6(ConstElementPtr subnet, ConstElementPtr pools) { + Adaptor::fromParent("valid-lifetime", subnet, pools); + Adaptor::fromParent("preferred-lifetime", subnet, pools); + Adaptor::fromParent("renew-timer", subnet, pools); + Adaptor::fromParent("rebind-timer", subnet, pools); +} + +void +AdaptorPool::toSubnet(const string& model, ElementPtr subnet, + ConstElementPtr pools) { + if (model == IETF_DHCPV6_SERVER) { + toSubnetIetf6(subnet, pools); + } else if ((model != KEA_DHCP4_SERVER) && + (model != KEA_DHCP6_SERVER)) { + isc_throw(NotImplemented, + "toSubnet not implemented for the model: " << model); + } +} + +void +AdaptorPool::toSubnetIetf6(ElementPtr subnet, ConstElementPtr pools) { + Adaptor::toParent("valid-lifetime", subnet, pools); + Adaptor::toParent("preferred-lifetime", subnet, pools); + Adaptor::toParent("renew-timer", subnet, pools); + Adaptor::toParent("rebind-timer", subnet, pools); +} + +}; // end of namespace isc::yang +}; // end of namespace isc diff --git a/src/lib/yang/adaptor_pool.h b/src/lib/yang/adaptor_pool.h new file mode 100644 index 0000000000..9d19c23f1a --- /dev/null +++ b/src/lib/yang/adaptor_pool.h @@ -0,0 +1,91 @@ +// Copyright (C) 2018 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 ISC_ADAPTOR_POOL_H +#define ISC_ADAPTOR_POOL_H 1 + +#include +#include + +namespace isc { +namespace yang { + +/// @brief JSON adaptor for pools between canonical Kea and YANG models. +/// +/// First adaptor canonizePool checks and fixes if needed the pool entry +/// to a canonical form which is no space for prefix and one space each +/// side of the minus character for ranges. +/// +/// Second adaptor is specific to the IETF DHCPv6 model (and does nothing +/// with a Kea DHCP model): it moves timer definitions from the subnet scope, +/// i.e. the scope where they are in Kea, to the pool scope, i.e. the scope +/// where they are in the IETF model, and back. The from way leaves timers +/// in the subnet scope as they are ignored by the translator, the to way +/// removes timers from pools as they are not expected by Kea at this scope. +class AdaptorPool { +public: + + /// @brief Constructor. + AdaptorPool(); + + /// @brief Destructor. + virtual ~AdaptorPool(); + + /// @brief Canonize pool. + /// + /// Remove spaces and replace "-" by " - " for readability. + /// @param pool The pool. + static void canonizePool(isc::data::ElementPtr pool); + + /// @brief From subnets. + /// + /// Move parameters from the subnet to each pool. + /// + /// @param model Model name. + /// @param subnet The subnet element. + /// @param pools The children pools. + /// @throw NotImplemented on unexpected model. + static void fromSubnet(const std::string& model, + isc::data::ConstElementPtr subnet, + isc::data::ConstElementPtr pools); + + /// @brief To subnet. + /// + /// Move parameters from pools to the subnet. + /// + /// @param model Model name. + /// @param subnet The subnet element. + /// @param pools The children pools. + /// @throw NotImplemented on unexpected model. + /// @throw BadValue on inconsistent (different timer values) pools. + static void toSubnet(const std::string& model, + isc::data::ElementPtr subnet, + isc::data::ConstElementPtr pools); + +protected: + /// @brief From subnets for ietf-dhcpv6-server. + /// + /// Use common and move valid-lifetime and preferred-lifetime. + /// + /// @param subnet The subnet element. + /// @param pools The children pools. + static void fromSubnetIetf6(isc::data::ConstElementPtr subnet, + isc::data::ConstElementPtr pools); + + /// @brief To subnet for ietf-dhcpv6-server. + /// + /// Use common and move valid-lifetime and preferred-lifetime. + /// + /// @param subnet The subnet element. + /// @param pools The children pools. + static void toSubnetIetf6(isc::data::ElementPtr subnet, + isc::data::ConstElementPtr pools); +}; + +}; // end of namespace isc::yang +}; // end of namespace isc + +#endif // ISC_ADAPTOR_POOL_H diff --git a/src/lib/yang/adaptor_subnet.cc b/src/lib/yang/adaptor_subnet.cc new file mode 100644 index 0000000000..862e6b130d --- /dev/null +++ b/src/lib/yang/adaptor_subnet.cc @@ -0,0 +1,71 @@ +// Copyright (C) 2018 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 + +using namespace std; +using namespace isc::data; +using namespace isc::dhcp; + +namespace isc { +namespace yang { + +AdaptorSubnet::AdaptorSubnet() { +} + +AdaptorSubnet::~AdaptorSubnet() { +} + +bool +AdaptorSubnet::collectID(ConstElementPtr subnet, SubnetIDSet& set) { + ConstElementPtr id = subnet->get("id"); + if (id) { + set.insert(static_cast(id->intValue())); + return (true); + } + return (false); +} + +void +AdaptorSubnet::assignID(ElementPtr subnet, SubnetIDSet& set, SubnetID& next) { + ConstElementPtr id = subnet->get("id"); + if (!id) { + // Skip already used. + while (set.count(next) > 0) { + ++next; + } + subnet->set("id", Element::create(static_cast(next))); + set.insert(next); + ++next; + } +} + +void +AdaptorSubnet::updateRelay(ElementPtr subnet) { + ConstElementPtr relay = subnet->get("relay"); + if (!relay) { + return; + } + ConstElementPtr addresses = relay->get("ip-addresses"); + if (!addresses) { + ConstElementPtr address = relay->get("ip-address"); + if (!address) { + subnet->remove("relay"); + return; + } + ElementPtr addr = Element::create(address->stringValue()); + ElementPtr addrs = Element::createList(); + addrs->add(addr); + ElementPtr updated = Element::createMap(); + updated->set("ip-addresses", addrs); + subnet->set("relay", updated); + } else if (addresses->size() == 0) { + subnet->remove("relay"); + } +} + +}; // end of namespace isc::yang +}; // end of namespace isc diff --git a/src/lib/yang/adaptor_subnet.h b/src/lib/yang/adaptor_subnet.h new file mode 100644 index 0000000000..d6ad80b2ca --- /dev/null +++ b/src/lib/yang/adaptor_subnet.h @@ -0,0 +1,66 @@ +// Copyright (C) 2018 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 ISC_ADAPTOR_SUBNET_H +#define ISC_ADAPTOR_SUBNET_H 1 + +#include +#include +#include + +namespace isc { +namespace yang { + +/// @brief Set of SubnetIDs. +typedef std::set SubnetIDSet; + +/// @brief JSON adaptor for subnets adding IDs and canonizes relays. +/// +/// Adding IDs is done in two passes walking through subnets. +/// -1- Add in the set used values and return false when there is no ID +/// so the caller can decide if the second pass is needed. +/// -2- For a subnet without an ID, assigned the next unused ID. +/// +/// For relays an old syntax ip-address is translated into a new syntax +/// ip-addresses. Note as all canonization adaptor it is optional, i.e., +/// code should work without it. +class AdaptorSubnet { +public: + + /// @brief Constructor. + AdaptorSubnet(); + + /// @brief Destructor. + virtual ~AdaptorSubnet(); + + /// @brief Collect a subnet ID. + /// + /// @param subnet The subnet. + /// @param set The reference to the set of assigned IDs. + /// @return True if the subnet has an ID, false otherwise. + static bool collectID(isc::data::ConstElementPtr subnet, SubnetIDSet& set); + + /// @brief Assign subnet ID. + /// + /// @param subnet The subnet. + /// @param set The reference to the set of assigned IDs. + /// @param next The next ID. + static void assignID(isc::data::ElementPtr subnet, SubnetIDSet& set, + isc::dhcp::SubnetID& next); + + /// @brief Update relay. + /// + /// Force the use of ip-addresses vs. ip-address. + /// Can be used for shared networks too. + /// + /// @param subnet The subnet. + static void updateRelay(isc::data::ElementPtr subnet); +}; + +}; // end of namespace isc::yang +}; // end of namespace isc + +#endif // ISC_ADAPTOR_SUBNET_H diff --git a/src/lib/yang/tests/Makefile.am b/src/lib/yang/tests/Makefile.am index 2514036a39..320987ef98 100644 --- a/src/lib/yang/tests/Makefile.am +++ b/src/lib/yang/tests/Makefile.am @@ -18,6 +18,10 @@ TESTS = if HAVE_GTEST TESTS += run_unittests run_unittests_SOURCES = adaptor_unittests.cc +run_unittests_SOURCES += adaptor_option_unittests.cc +run_unittests_SOURCES += adaptor_pool_unittests.cc +run_unittests_SOURCES += adaptor_host_unittests.cc +run_unittests_SOURCES += adaptor_subnet_unittests.cc run_unittests_SOURCES += sysrepo_setup.h run_unittests_SOURCES += translator_unittests.cc run_unittests_SOURCES += translator_control_socket_unittests.cc diff --git a/src/lib/yang/tests/adaptor_host_unittests.cc b/src/lib/yang/tests/adaptor_host_unittests.cc new file mode 100644 index 0000000000..0e7f91c3b3 --- /dev/null +++ b/src/lib/yang/tests/adaptor_host_unittests.cc @@ -0,0 +1,118 @@ +// Copyright (C) 2018 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 + +using namespace std; +using namespace isc; +using namespace isc::data; +using namespace isc::yang; + +namespace { + +// Verifies that quoteIdentifier does not touch an identifier which +// has a type different from flex-d. +TEST(AdaptorHostTest, notFlexId) { + string config = "{\n" + " \"hw-address\": \"1a:1b:1c:1d:1e:1f\",\n" + " \"ip-address\": \"192.0.2.201\"\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + EXPECT_NO_THROW(AdaptorHost::quoteIdentifier(json)); + EXPECT_TRUE(copied->equals(*json)); +} + +// Verifies that quoteIdentifier does not touch a flex-id identifier +// without quotes. +TEST(AdaptorHostTest, noQuote) { + string config = "{\n" + " \"flex-id\": \"s0mEVaLue\",\n" + " \"ip-address\": \"192.0.2.206\"\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + EXPECT_NO_THROW(AdaptorHost::quoteIdentifier(json)); + EXPECT_TRUE(copied->equals(*json)); +} + +// Verifies that quoteIdentifier removes quotes from a flex-id identifier. +TEST(AdaptorHostTest, quotes) { + string config = "{\n" + " \"flex-id\": \"'somevalue'\",\n" + " \"ip-addresses\": \"2001:db8:1:cafe::2\"\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + EXPECT_NO_THROW(AdaptorHost::quoteIdentifier(json)); + EXPECT_FALSE(copied->equals(*json)); + ConstElementPtr id = json->get("flex-id"); + ASSERT_TRUE(id); + ASSERT_EQ(Element::string, id->getType()); + EXPECT_EQ("73:6f:6d:65:76:61:6c:75:65", id->stringValue()); +} + +// Verifies that quoteIdentifier removes quotes from a flex-id identifier +// but does not interpret a quote in the middle. +TEST(AdaptorHostTest, extraQuote) { + string config = "{\n" + " \"flex-id\": \"'some'value'\",\n" + " \"ip-addresses\": \"2001:db8:1:cafe::2\"\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + EXPECT_NO_THROW(AdaptorHost::quoteIdentifier(json)); + EXPECT_FALSE(copied->equals(*json)); + ConstElementPtr id = json->get("flex-id"); + ASSERT_TRUE(id); + ASSERT_EQ(Element::string, id->getType()); + EXPECT_EQ("73:6f:6d:65:27:76:61:6c:75:65", id->stringValue()); +} + +// Verifies that quoteIdentifier works on not standard characters too. +TEST(AdaptorHostTest, notStandard) { + string config = "{\n" + " \"flex-id\": \"'some\\\"value'\",\n" + " \"ip-addresses\": \"2001:db8:1:cafe::2\"\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + EXPECT_NO_THROW(AdaptorHost::quoteIdentifier(json)); + EXPECT_FALSE(copied->equals(*json)); + ConstElementPtr id = json->get("flex-id"); + ASSERT_TRUE(id); + ASSERT_EQ(Element::string, id->getType()); + EXPECT_EQ("73:6f:6d:65:22:76:61:6c:75:65", id->stringValue()); +} + +// Verifies that quoteIdentifier works on not standard characters too +// even without quotes. +TEST(AdaptorHostTest, notQuoted) { + string config = "{\n" + " \"flex-id\": \"some\\\"value\",\n" + " \"ip-addresses\": \"2001:db8:1:cafe::2\"\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + EXPECT_NO_THROW(AdaptorHost::quoteIdentifier(json)); + EXPECT_FALSE(copied->equals(*json)); + ConstElementPtr id = json->get("flex-id"); + ASSERT_TRUE(id); + ASSERT_EQ(Element::string, id->getType()); + EXPECT_EQ("73:6f:6d:65:22:76:61:6c:75:65", id->stringValue()); +} + +}; // end of anonymous namespace diff --git a/src/lib/yang/tests/adaptor_option_unittests.cc b/src/lib/yang/tests/adaptor_option_unittests.cc new file mode 100644 index 0000000000..39831c3ef1 --- /dev/null +++ b/src/lib/yang/tests/adaptor_option_unittests.cc @@ -0,0 +1,240 @@ +// Copyright (C) 2018 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 + +using namespace std; +using namespace isc; +using namespace isc::data; +using namespace isc::dhcp; +using namespace isc::yang; + +namespace { + +// Verifies that setSpace adds a space to an option without this entry. +TEST(AdaptorOptionTest, setSpaceNoSpace) { + string config = "{\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + EXPECT_NO_THROW(AdaptorOption::setSpace(json, "foo")); + EXPECT_FALSE(copied->equals(*json)); + ConstElementPtr space = json->get("space"); + ASSERT_TRUE(space); + ASSERT_EQ(Element::string, space->getType()); + EXPECT_EQ("foo", space->stringValue()); +} + +// Verifies that setSpace does not change to an option with space. +TEST(AdaptorOptionTest, setSpace) { + string config = "{\n" + " \"space\": \"dhcp4\"\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + EXPECT_NO_THROW(AdaptorOption::setSpace(json, "foo")); + EXPECT_TRUE(copied->equals(*json)); +} + +// Verifies that checkType accepts an option with type. +TEST(AdaptorOptionTest, checkType) { + string config = "{\n" + " \"type\": \"string\"\n" + "}"; + ConstElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + EXPECT_NO_THROW(AdaptorOption::checkType(json)); +} + +// Verifies that checkType does not accept an option without type. +TEST(AdaptorOptionTest, checkTypeNoType) { + string config = "{\n" + "}"; + ConstElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + EXPECT_THROW(AdaptorOption::checkType(json), MissingKey); +} + +// Verifies that checkCode accepts an option with code. +TEST(AdaptorOptionTest, checkCode) { + string config = "{\n" + " \"code\": 123\n" + "}"; + ConstElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + EXPECT_NO_THROW(AdaptorOption::checkCode(json)); +} + +// Verifies that checkCode does not accept an option without code. +TEST(AdaptorOptionTest, checkCodeNoCode) { + string config = "{\n" + "}"; + ConstElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + EXPECT_THROW(AdaptorOption::checkCode(json), MissingKey); +} + +// Verifies that collect works as expected. +TEST(AdaptorOptionTest, collect) { + string config = "{\n" + " \"code\": 123,\n" + " \"name\": \"foo\",\n" + " \"space\": \"bar\"\n" + "}"; + ConstElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + OptionCodes codes; + ASSERT_NO_THROW(AdaptorOption::collect(json, codes)); + EXPECT_EQ(1, codes.size()); + EXPECT_EQ(123, codes["bar@foo"]); + EXPECT_THROW(codes.at("foo@bar"), out_of_range); +} + +// Verifies that collect skips an already known option definition. +TEST(AdaptorOptionTest, collectKnown) { + string config = "{\n" + " \"code\": 123,\n" + " \"name\": \"foo\",\n" + " \"space\": \"bar\"\n" + "}"; + ConstElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + OptionCodes codes = { { "bar@foo", 111 } }; + ASSERT_NO_THROW(AdaptorOption::collect(json, codes)); + EXPECT_EQ(1, codes.size()); + EXPECT_EQ(111, codes["bar@foo"]); +} + +// Verifies that setCode adds a code to an option without this entry +// when the code is available in the map. +TEST(AdaptorOptionTest, setCodeNoCode) { + string config = "{\n" + " \"name\": \"foo\",\n" + " \"space\": \"bar\"\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + OptionCodes codes = { { "bar@foo", 123 } }; + EXPECT_NO_THROW(AdaptorOption::setCode(json, codes)); + EXPECT_FALSE(copied->equals(*json)); + ConstElementPtr code = json->get("code"); + ASSERT_TRUE(code); + ASSERT_EQ(Element::integer, code->getType()); + EXPECT_EQ(123, code->intValue()); +} + +// Verifies that setCode does not change to an option with code. +TEST(AdaptorOptionTest, setCode) { + string config = "{\n" + " \"code\": \"dhcp4\"\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + OptionCodes codes; + EXPECT_NO_THROW(AdaptorOption::setCode(json, codes)); + EXPECT_TRUE(copied->equals(*json)); +} + +// Verifies that setCode raises an error on option without name and code. +TEST(AdaptorOptionTest, setCodeNoName) { + string config = "{\n" + " \"space\": \"bar\"\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + OptionCodes codes; + EXPECT_THROW(AdaptorOption::setCode(json, codes), MissingKey); +} + +// Note the code assumes there is a space, i.e. setSpace was called. + +// Verifies that setCode raises an error on option without code but +// the code is not available in the map. +TEST(AdaptorOptionTest, setCodeNotInMap) { + string config = "{\n" + " \"name\": \"foo\",\n" + " \"space\": \"bar\"\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + OptionCodes codes; + EXPECT_THROW(AdaptorOption::setCode(json, codes), MissingKey); +} + +/// @brief Test class to make initCodes public. +class TestAdaptorOption : public AdaptorOption { +public: + using AdaptorOption::initCodesInternal; +}; + +// Verifies that initCodesInternal works as expected. +TEST(AdaptorOptionTest, initCodesInternal) { + const OptionDefParams DEFS[] = { + { "subnet-mask", DHO_SUBNET_MASK, OPT_IPV4_ADDRESS_TYPE, + false, 0, 0, "" }, + { "time-offset", DHO_TIME_OFFSET, OPT_INT32_TYPE, + false, 0, 0, "" } + }; + const size_t DEFS_SIZE = sizeof(DEFS) / sizeof(DEFS[0]); + OptionCodes codes; + ASSERT_NO_THROW(TestAdaptorOption::initCodesInternal(codes, + "dhcp4", + DEFS, + DEFS_SIZE)); + ASSERT_EQ(2, codes.size()); + EXPECT_EQ(DHO_SUBNET_MASK, codes["dhcp4@subnet-mask"]); + EXPECT_EQ(DHO_TIME_OFFSET, codes["dhcp4@time-offset"]); +} + +// Verifies that initCodes works as expected with DHCPv4. +TEST(AdaptorOptionTest, initCodes4) { + OptionCodes codes; + ASSERT_NO_THROW(AdaptorOption::initCodes(codes, DHCP4_OPTION_SPACE)); + EXPECT_EQ(DHO_SUBNET_MASK, codes["dhcp4@subnet-mask"]); + EXPECT_EQ(DHO_TIME_OFFSET, codes["dhcp4@time-offset"]); + EXPECT_THROW(codes.at("dhcp6@clientid"), out_of_range); + + // initCodes loads last resort too. + EXPECT_EQ(DHO_VENDOR_ENCAPSULATED_OPTIONS, + codes["dhcp4@vendor-encapsulated-options"]); + + // and DOCSIS3. + EXPECT_EQ(2, codes["vendor-4491@tftp-servers"]); +} + +// Verifies that initCodes works as expected with DHCPv6. +TEST(AdaptorOptionTest, initCodes6) { + OptionCodes codes; + ASSERT_NO_THROW(AdaptorOption::initCodes(codes, DHCP6_OPTION_SPACE)); + EXPECT_EQ(D6O_CLIENTID, codes["dhcp6@clientid"]); + EXPECT_EQ(D6O_SERVERID, codes["dhcp6@serverid"]); + EXPECT_THROW(codes.at("dhcp4@subnet-mask"), out_of_range); + + // initCodes loads DOCSIS3 too. + EXPECT_EQ(32, codes["vendor-4491@tftp-servers"]); + + // Various MAP suboptions. + EXPECT_EQ(D6O_S46_BR, codes["s46-cont-mape-options@s46-br"]); + EXPECT_EQ(D6O_S46_V4V6BIND, codes["s46-cont-lw-options@s46-v4v6bind"]); + EXPECT_EQ(D6O_S46_PORTPARAMS, codes["s46-v4v6bind-options@s46-portparams"]); + + // And ISC space. + EXPECT_EQ(ISC_V6_4O6_INTERFACE, codes["vendor-2495@4o6-interface"]); +} + +}; // end of anonymous namespace diff --git a/src/lib/yang/tests/adaptor_pool_unittests.cc b/src/lib/yang/tests/adaptor_pool_unittests.cc new file mode 100644 index 0000000000..564d037bf8 --- /dev/null +++ b/src/lib/yang/tests/adaptor_pool_unittests.cc @@ -0,0 +1,270 @@ +// Copyright (C) 2018 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 + +using namespace std; +using namespace isc; +using namespace isc::data; +using namespace isc::yang; + +namespace { + +// Verifies that canonizePool does not touch a prefix without space. +TEST(AdaptorPoolTest, canonizePoolPrefixNoSpace) { + string config = "{\n" + " \"pool\": \"192.0.2.128/28\"\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + EXPECT_NO_THROW(AdaptorPool::canonizePool(json)); + EXPECT_TRUE(copied->equals(*json)); +} + +// Verifies that canonizePool does not touch a canonical range. +TEST(AdaptorPoolTest, canonizePoolRange) { + string config = "{\n" + " \"pool\": \"192.0.2.1 - 192.0.2.200\"\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + EXPECT_NO_THROW(AdaptorPool::canonizePool(json)); + EXPECT_TRUE(copied->equals(*json)); +} + +// Verifies that canonizePool removes spaces from a prefix. +TEST(AdaptorPoolTest, canonizePoolPrefixSpaces) { + string config = "{\n" + " \"pool\": \"192.0.2.128 /\t28\"\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + EXPECT_NO_THROW(AdaptorPool::canonizePool(json)); + EXPECT_FALSE(copied->equals(*json)); + ConstElementPtr pool = json->get("pool"); + ASSERT_TRUE(pool); + ASSERT_EQ(Element::string, pool->getType()); + EXPECT_EQ("192.0.2.128/28", pool->stringValue()); +} + +// Verifies that canonizePool adds two spaces from a range. +TEST(AdaptorPoolTest, canonizePoolRangeNoSpace) { + string config = "{\n" + " \"pool\": \"192.0.2.1-192.0.2.200\"\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + EXPECT_NO_THROW(AdaptorPool::canonizePool(json)); + EXPECT_FALSE(copied->equals(*json)); + ConstElementPtr pool = json->get("pool"); + ASSERT_TRUE(pool); + ASSERT_EQ(Element::string, pool->getType()); + EXPECT_EQ("192.0.2.1 - 192.0.2.200", pool->stringValue()); +} + +// Verifies that canonizePool removes extra spaces from a range. +TEST(AdaptorPoolTest, canonizePoolRangeExtraSpaces) { + string config = "{\n" + " \"pool\": \"192.0.2.1 - 192.0.2.200\"\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + EXPECT_NO_THROW(AdaptorPool::canonizePool(json)); + EXPECT_FALSE(copied->equals(*json)); + ConstElementPtr pool = json->get("pool"); + ASSERT_TRUE(pool); + ASSERT_EQ(Element::string, pool->getType()); + EXPECT_EQ("192.0.2.1 - 192.0.2.200", pool->stringValue()); +} + +// Verifies that fromSubnet is specific to ietf-dhcpv6-server model. +TEST(AdaptorPoolTest, fromSubnetKea) { + string config = "{\n" + " \"subnet\": \"192.0.2.0/24\",\n" + " \"pools\": [\n" + " {\n" + " \"pool\": \"192.0.2.1 - 192.0.2.100\"\n" + " },{\n" + " \"pool\": \"192.0.2.101 - 192.0.2.200\"\n" + " } ],\n" + " \"valid-lifetime\": 4000,\n" + " \"renew-timer\": 1000,\n" + " \"rebind-timer\": 2000\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + ConstElementPtr pools = json->get("pools"); + EXPECT_NO_THROW(AdaptorPool::fromSubnet(KEA_DHCP4_SERVER, json, pools)); + EXPECT_TRUE(copied->equals(*json)); + // The model is checked first. + EXPECT_NO_THROW(AdaptorPool::fromSubnet(KEA_DHCP6_SERVER, json, pools)); + EXPECT_TRUE(copied->equals(*json)); + // Model name is not free: an error is raised if it is not expected. + EXPECT_THROW(AdaptorPool::fromSubnet("keatest-module", json, pools), + NotImplemented); +} + +// Verifies that fromSubnet works as expected. +TEST(AdaptorPoolTest, fromSubnet) { + string config = "{\n" + " \"subnet\": \"2001:db8:1::/64\",\n" + " \"pools\": [\n" + " {\n" + " \"pool\": \"2001:db8:1::/80\"\n" + " },{\n" + " \"pool\": \"2001:db8:1:0:1::/80\"\n" + " } ],\n" + " \"preferred-lifetime\": 3000,\n" + " \"valid-lifetime\": 4000,\n" + " \"renew-timer\": 1000,\n" + " \"rebind-timer\": 2000\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + ConstElementPtr pools = json->get("pools"); + EXPECT_NO_THROW(AdaptorPool::fromSubnet(IETF_DHCPV6_SERVER, json, pools)); + EXPECT_FALSE(copied->equals(*json)); + pools = json->get("pools"); + ASSERT_TRUE(pools); + ASSERT_EQ(2, pools->size()); + ConstElementPtr pool = pools->get(0); + ASSERT_TRUE(pool); + string expected = "{" + " \"pool\": \"2001:db8:1::/80\"," + " \"preferred-lifetime\": 3000," + " \"rebind-timer\": 2000," + " \"renew-timer\": 1000," + " \"valid-lifetime\": 4000 }"; + EXPECT_EQ(expected, pool->str()); + pool = pools->get(1); + ASSERT_TRUE(pool); + expected = "{" + " \"pool\": \"2001:db8:1:0:1::/80\"," + " \"preferred-lifetime\": 3000," + " \"rebind-timer\": 2000," + " \"renew-timer\": 1000," + " \"valid-lifetime\": 4000 }"; + EXPECT_EQ(expected, pool->str()); +} + +// Verifies that toSubnet is specific to ietf-dhcpv6-server model. +TEST(AdaptorPoolTest, toSubnetKea) { + string config = "{\n" + " \"subnet\": \"192.0.2.0/24\",\n" + " \"pools\": [\n" + " {\n" + " \"pool\": \"192.0.2.1 - 192.0.2.100\"\n" + " },{\n" + " \"pool\": \"192.0.2.101 - 192.0.2.200\"\n" + " } ],\n" + " \"valid-lifetime\": 4000,\n" + " \"renew-timer\": 1000,\n" + " \"rebind-timer\": 2000\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + ConstElementPtr pools = json->get("pools"); + EXPECT_NO_THROW(AdaptorPool::toSubnet(KEA_DHCP4_SERVER, json, pools)); + EXPECT_TRUE(copied->equals(*json)); + // The model is checked first. + EXPECT_NO_THROW(AdaptorPool::toSubnet(KEA_DHCP6_SERVER, json, pools)); + EXPECT_TRUE(copied->equals(*json)); + // Model name is not free: an error is raised if it is not expected. + EXPECT_THROW(AdaptorPool::toSubnet("keatest-module", json, pools), + NotImplemented); +} + +// Verifies that toSubnet works as expected. +TEST(AdaptorPoolTest, toSubnet) { + string config = "{\n" + " \"subnet\": \"2001:db8:1::/64\",\n" + " \"pools\": [\n" + " {\n" + " \"pool\": \"2001:db8:1::/80\",\n" + " \"preferred-lifetime\": 3000,\n" + " \"valid-lifetime\": 4000,\n" + " \"renew-timer\": 1000,\n" + " \"rebind-timer\": 2000\n" + " },{\n" + " \"pool\": \"2001:db8:1:0:1::/80\",\n" + " \"preferred-lifetime\": 3000,\n" + " \"valid-lifetime\": 4000,\n" + " \"renew-timer\": 1000,\n" + " \"rebind-timer\": 2000\n" + " } ]\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + ConstElementPtr pools = json->get("pools"); + EXPECT_NO_THROW(AdaptorPool::toSubnet(IETF_DHCPV6_SERVER, json, pools)); + EXPECT_FALSE(copied->equals(*json)); + // Check timers. + ConstElementPtr timer = json->get("valid-lifetime"); + ASSERT_TRUE(timer); + EXPECT_EQ("4000", timer->str()); + timer = json->get("preferred-lifetime"); + ASSERT_TRUE(timer); + EXPECT_EQ("3000", timer->str()); + timer = json->get("renew-timer"); + ASSERT_TRUE(timer); + EXPECT_EQ("1000", timer->str()); + timer = json->get("rebind-timer"); + ASSERT_TRUE(timer); + EXPECT_EQ("2000", timer->str()); + // Timers must be removed as they are not allowed here in Kea. + pools = json->get("pools"); + ASSERT_TRUE(pools); + ASSERT_EQ(2, pools->size()); + ConstElementPtr pool = pools->get(0); + ASSERT_TRUE(pool); + EXPECT_EQ("{ \"pool\": \"2001:db8:1::/80\" }", pool->str()); + pool = pools->get(1); + ASSERT_TRUE(pool); + EXPECT_EQ("{ \"pool\": \"2001:db8:1:0:1::/80\" }", pool->str()); +} + +// Verifies that toSubnet fails on inconsistent input. +TEST(AdaptorPoolTest, toSubnetBad) { + // Changed last rebind-timer to a different value. + string config = "{\n" + " \"subnet\": \"2001:db8:1::/64\",\n" + " \"pools\": [\n" + " {\n" + " \"pool\": \"2001:db8:1::/80\",\n" + " \"preferred-lifetime\": 3000,\n" + " \"valid-lifetime\": 4000,\n" + " \"renew-timer\": 1000,\n" + " \"rebind-timer\": 2000\n" + " },{\n" + " \"pool\": \"2001:db8:1:0:1::/80\",\n" + " \"preferred-lifetime\": 3000,\n" + " \"valid-lifetime\": 4000,\n" + " \"renew-timer\": 1000,\n" + " \"rebind-timer\": 20\n" + " } ]\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr pools = json->get("pools"); + EXPECT_THROW(AdaptorPool::toSubnet(IETF_DHCPV6_SERVER, json, pools), + BadValue); +} + +}; // end of anonymous namespace diff --git a/src/lib/yang/tests/adaptor_subnet_unittests.cc b/src/lib/yang/tests/adaptor_subnet_unittests.cc new file mode 100644 index 0000000000..04bd84dae0 --- /dev/null +++ b/src/lib/yang/tests/adaptor_subnet_unittests.cc @@ -0,0 +1,212 @@ +// Copyright (C) 2018 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 + +using namespace std; +using namespace isc; +using namespace isc::data; +using namespace isc::dhcp; +using namespace isc::yang; + +namespace { + +// Verifies how collectID handles a subnet entry without ID. +TEST(AdaptorSubnetTest, collectNoId) { + string config = "{\n" + " \"subnet\": \"192.0.2.0/24\"\n" + "}"; + ConstElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + SubnetIDSet set; + bool ret = true; + ASSERT_NO_THROW(ret = AdaptorSubnet::collectID(json, set)); + EXPECT_FALSE(ret); + EXPECT_EQ(0, set.size()); +} + +// Verifies how collectID handles a subnet entry with an ID. +TEST(AdaptorSubnetTest, collectId) { + string config = "{\n" + " \"subnet\": \"192.0.2.0/24\",\n" + " \"id\": 123\n" + "}"; + ConstElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + SubnetIDSet set; + bool ret = false; + ASSERT_NO_THROW(ret = AdaptorSubnet::collectID(json, set)); + EXPECT_TRUE(ret); + EXPECT_EQ(1, set.size()); + EXPECT_EQ(1, set.count(123)); +} + +// Verifies how collectID handles a subnet entry with an ID which is +// already known: the set is not updated. +TEST(AdaptorSubnetTest, collectKnownId) { + string config = "{\n" + " \"subnet\": \"192.0.2.0/24\",\n" + " \"id\": 123\n" + "}"; + ConstElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + SubnetIDSet set = { 123 }; + bool ret = false; + ASSERT_NO_THROW(ret = AdaptorSubnet::collectID(json, set)); + EXPECT_TRUE(ret); + EXPECT_EQ(1, set.size()); + EXPECT_EQ(1, set.count(123)); +} + +// Verifies how assignID handles a subnet entry without ID: the next ID +// is assigned, the set is updated and the next ID is incremented. +TEST(AdaptorSubnetTest, assignNoId) { + string config = "{\n" + " \"subnet\": \"192.0.2.0/24\"\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + SubnetIDSet set; + SubnetID next_id = 123; + ASSERT_NO_THROW(AdaptorSubnet::assignID(json, set, next_id)); + EXPECT_FALSE(copied->equals(*json)); + EXPECT_EQ(1, set.size()); + EXPECT_EQ(1, set.count(123)); + EXPECT_EQ(124, next_id); + ConstElementPtr id = json->get("id"); + ASSERT_TRUE(id); + ASSERT_EQ(Element::integer, id->getType()); + EXPECT_EQ(123, id->intValue()); +} + +// Verifies how assignID handles a subnet entry without ID but with the +// candidate ID already used: the used value is skipped. +TEST(AdaptorSubnetTest, assignNoIdUsed) { + string config = "{\n" + " \"subnet\": \"192.0.2.0/24\"\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + SubnetIDSet set = { 123 }; + SubnetID next_id = 123; + ASSERT_NO_THROW(AdaptorSubnet::assignID(json, set, next_id)); + EXPECT_FALSE(copied->equals(*json)); + EXPECT_EQ(2, set.size()); + EXPECT_EQ(1, set.count(123)); + EXPECT_EQ(1, set.count(124)); + EXPECT_EQ(125, next_id); + ConstElementPtr id = json->get("id"); + ASSERT_TRUE(id); + ASSERT_EQ(Element::integer, id->getType()); + EXPECT_EQ(124, id->intValue()); +} + +// Verifies how assignID handles a subnet entry with ID: no change. +TEST(AdaptorSubnetTest, assignId) { + string config = "{\n" + " \"subnet\": \"192.0.2.0/24\",\n" + " \"id\": 123\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + SubnetIDSet set; // ignored. + SubnetID next_id = 123; // ignored. + ASSERT_NO_THROW(AdaptorSubnet::assignID(json, set, next_id)); + EXPECT_TRUE(copied->equals(*json)); + EXPECT_EQ(0, set.size()); + EXPECT_EQ(123, next_id); +} + +// Verifies how updateRelay handles a subnet entry without relay: no change. +TEST(AdaptorSubnetTest, updateNoRelay) { + string config = "{\n" + " \"subnet\": \"192.0.2.0/24\"\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + ASSERT_NO_THROW(AdaptorSubnet::updateRelay(json)); + EXPECT_TRUE(copied->equals(*json)); +} + +// Verifies how updateRelay handles a subnet entry with empty relay: +// the relay entry is useless and removed. +TEST(AdaptorSubnetTest, updateEmptyRelay) { + string config = "{\n" + " \"subnet\": \"192.0.2.0/24\",\n" + " \"relay\": { }\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + ASSERT_NO_THROW(AdaptorSubnet::updateRelay(json)); + EXPECT_FALSE(copied->equals(*json)); + EXPECT_FALSE(json->get("relay")); +} + +// Verifies how updateRelay handles a subnet entry with relay which +// has empty addresses: the relay entry is useless and removed. +TEST(AdaptorSubnetTest, updateRelayEmptyAddresses) { + string config = "{\n" + " \"subnet\": \"192.0.2.0/24\",\n" + " \"relay\": {\n" + " \"ip-addresses\": [ ]\n" + " }\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + ASSERT_NO_THROW(AdaptorSubnet::updateRelay(json)); + EXPECT_FALSE(copied->equals(*json)); + EXPECT_FALSE(json->get("relay")); +} + +// Verifies how updateRelay handles a subnet entry with relay which +// has addresses: no change. +TEST(AdaptorSubnetTest, updateRelayAddresses) { + string config = "{\n" + " \"subnet\": \"192.0.2.0/24\",\n" + " \"relay\": {\n" + " \"ip-addresses\": [ \"192.168.1.1\" ]\n" + " }\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + ASSERT_NO_THROW(AdaptorSubnet::updateRelay(json)); + EXPECT_TRUE(copied->equals(*json)); +} + +// Verifies how updateRelay handles a subnet entry with relay which +// has only address: the address is moved to a new list. +TEST(AdaptorSubnetTest, updateRelayAddress) { + string config = "{\n" + " \"subnet\": \"192.0.2.0/24\",\n" + " \"relay\": {\n" + " \"ip-address\": \"192.168.1.1\"\n" + " }\n" + "}"; + ElementPtr json; + ASSERT_NO_THROW(json = Element::fromJSON(config)); + ConstElementPtr copied = copy(json); + ASSERT_NO_THROW(AdaptorSubnet::updateRelay(json)); + EXPECT_FALSE(copied->equals(*json)); + ConstElementPtr relay = json->get("relay"); + ASSERT_TRUE(relay); + string expected = "{ \"ip-addresses\": [ \"192.168.1.1\" ] }"; + EXPECT_EQ(expected, relay->str()); +} + +// It does not make sense to have both ip-address and ip-addresses... + +}; // end of anonymous namespace