summaryrefslogtreecommitdiffstats
path: root/src/lib/yang/tests/config_unittests.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/yang/tests/config_unittests.cc')
-rw-r--r--src/lib/yang/tests/config_unittests.cc417
1 files changed, 417 insertions, 0 deletions
diff --git a/src/lib/yang/tests/config_unittests.cc b/src/lib/yang/tests/config_unittests.cc
new file mode 100644
index 0000000..7a655a7
--- /dev/null
+++ b/src/lib/yang/tests/config_unittests.cc
@@ -0,0 +1,417 @@
+// Copyright (C) 2018-2022 Internet Systems Consortium, Inc. ("ISC")
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#include <config.h>
+
+#include <testutils/io_utils.h>
+#include <testutils/user_context_utils.h>
+#include <yang/translator_config.h>
+#include <yang/yang_models.h>
+#include <yang/tests/json_configs.h>
+#include <yang/tests/yang_configs.h>
+#include <yang/tests/sysrepo_setup.h>
+
+#include <boost/algorithm/string.hpp>
+
+#include <gtest/gtest.h>
+
+#include <iostream>
+
+using namespace std;
+using namespace isc;
+using namespace isc::data;
+using namespace isc::yang;
+using namespace isc::yang::test;
+using namespace sysrepo;
+
+namespace {
+
+/// @brief Return the difference between two strings
+///
+/// Use the gtest >= 1.8.0 tool which builds the difference between
+/// two vectors of lines.
+///
+/// @param left left string
+/// @param right right string
+/// @return the unified diff between left and right
+#ifdef HAVE_CREATE_UNIFIED_DIFF
+std::string generateDiff(std::string left, std::string right) {
+ std::vector<std::string> left_lines;
+ boost::split(left_lines, left, boost::is_any_of("\n"));
+ std::vector<std::string> right_lines;
+ boost::split(right_lines, right, boost::is_any_of("\n"));
+ using namespace testing::internal;
+ return (edit_distance::CreateUnifiedDiff(left_lines, right_lines));
+}
+#else
+std::string generateDiff(std::string, std::string) {
+ return ("");
+}
+#endif
+
+/// @brief Test Fixture class for Yang <-> JSON configs.
+class ConfigTest : public ::testing::Test {
+public:
+ virtual ~ConfigTest() = default;
+
+ void SetUp() override {
+ SysrepoSetup::cleanSharedMemory();
+ connection_ = std::make_shared<Connection>();
+ session_.reset(new Session(connection_, SR_DS_CANDIDATE));
+ translator_.reset(new TranslatorBasic(session_, model_));
+ cleanModelData();
+ }
+
+ void TearDown() override {
+ cleanModelData();
+ translator_.reset();
+ session_.reset();
+ connection_.reset();
+ SysrepoSetup::cleanSharedMemory();
+ }
+
+ void cleanModelData() {
+ std::string toplevel_node("config");
+ if (model_ == IETF_DHCPV6_SERVER) {
+ toplevel_node = "server";
+ }
+ translator_->delItem("/" + model_ + ":" + toplevel_node);
+ }
+
+ /// @brief Reset session.
+ void resetSession() {
+ SetUp();
+ }
+
+ /// @brief Loads YANG configuration from specified tree.
+ ///
+ /// @param tree The Yang tree to load.
+ void load(const YRTree& tree) {
+ YangRepr repr(model_);
+ repr.set(tree, session_);
+ }
+
+ /// @brief Loads JSON configuration from specified Element tree.
+ ///
+ /// @param json The JSON tree to load.
+ void load(ConstElementPtr json) {
+ TranslatorConfig tc(session_, model_);
+ tc.setConfig(json);
+ }
+
+ /// @brief Load a cofiguration from a string containing JSON.
+ ///
+ /// @param config The JSON tree to load in textual format.
+ void load(const string& config) {
+ ElementPtr json;
+ ASSERT_NO_THROW_LOG(json = Element::fromJSON(config));
+ load(json);
+ }
+
+ /// @brief Load a configuration from JSON file.
+ ///
+ /// @param filename The name of the JSON file to load,
+ ConstElementPtr loadFile(const string& filename) {
+ string decommented = isc::test::decommentJSONfile(filename);
+ ConstElementPtr json = Element::fromJSONFile(decommented, true);
+ ::remove(decommented.c_str());
+ load(json);
+ return (json);
+ }
+
+ /// @brief Returns YANG tree configuration.
+ YRTree getYang() {
+ YangRepr repr(model_);
+ return (repr.get(session_));
+ }
+
+ /// @brief Returns configuration in JSON (translated by TranslatorConfig)
+ ConstElementPtr getJSON() {
+ TranslatorConfig tc(session_, model_);
+ return (tc.getConfig());
+ }
+
+ /// @brief Retrieves configuration as text (in pretty JSON format).
+ string getText() {
+ return (isc::data::prettyPrint(getJSON()));
+ }
+
+ /// @brief Verify Yang.
+ ///
+ /// @param expected The expected Yang tree.
+ bool verify(const YRTree& expected) {
+ YangRepr repr(model_);
+ return (repr.verify(expected, session_, cerr));
+ }
+
+ /// @brief Verify JSON.
+ ///
+ /// @param expected The expected JSON tree.
+ bool verify(ConstElementPtr expected) {
+ TranslatorConfig tc(session_, model_);
+ ConstElementPtr content = tc.getConfig();
+ if (isEquivalent(expected, content)) {
+ return (true);
+ }
+ string wanted = prettyPrint(expected);
+ string got = prettyPrint(content);
+ cerr << "Expected:\n" << wanted << "\n"
+ << "Actual:\n" << got
+#ifdef HAVE_CREATE_UNIFIED_DIFF
+ << "\nDiff:\n" << generateDiff(wanted, got)
+#endif
+ << "\n";
+ return (false);
+ }
+
+ /// @brief Verify JSON.
+ ///
+ /// @param expected The expected JSON tree in textual format.
+ bool verify(const string& config) {
+ ConstElementPtr expected;
+ expected = Element::fromJSON(config);
+ return (verify(expected));
+ }
+
+ /// @brief Validate.
+ ///
+ /// @note A tree must be loaded first.
+ ///
+ bool validate() {
+ YangRepr repr(model_);
+ return (repr.validate(session_, cerr));
+ }
+
+ /// @brief The model.
+ string model_;
+
+ /// @brief The sysrepo connection.
+ S_Connection connection_;
+
+ /// @brief The sysrepo session.
+ S_Session session_;
+
+ std::unique_ptr<TranslatorBasic> translator_;
+};
+
+struct ConfigTestKeaV4 : ConfigTest {
+ ConfigTestKeaV4() {
+ model_ = KEA_DHCP4_SERVER;
+ }
+};
+struct ConfigTestKeaV6 : ConfigTest {
+ ConfigTestKeaV6() {
+ model_ = KEA_DHCP6_SERVER;
+ }
+};
+struct ConfigTestIetfV6 : ConfigTest {
+ ConfigTestIetfV6() {
+ model_ = IETF_DHCPV6_SERVER;
+ }
+};
+
+// Check empty config with ietf-dhcpv6-server model.
+TEST_F(ConfigTestIetfV6, emptyIetf6) {
+ YRTree tree;
+ ASSERT_NO_THROW_LOG(load(tree));
+ EXPECT_TRUE(verify(tree));
+
+ ConstElementPtr json = Element::fromJSON(emptyJson6);
+ EXPECT_TRUE(verify(json));
+ ASSERT_NO_THROW_LOG(load(json));
+ EXPECT_TRUE(verify(emptyJson6));
+ EXPECT_TRUE(verify(tree));
+}
+
+// Check empty config with kea-dhcp4-server:config model.
+TEST_F(ConfigTestKeaV4, emptyKeaDhcp4) {
+ YRTree tree;
+ ASSERT_NO_THROW_LOG(load(tree));
+ EXPECT_TRUE(verify(emptyTreeKeaDhcp4));
+
+ ConstElementPtr json = Element::fromJSON(emptyJson4);
+ EXPECT_TRUE(verify(json));
+ ASSERT_NO_THROW_LOG(load(json));
+ EXPECT_TRUE(verify(emptyJson4));
+ EXPECT_TRUE(verify(emptyTreeKeaDhcp4));
+}
+
+// Check empty config with kea-dhcp6-server:config model.
+TEST_F(ConfigTestKeaV6, emptyKeaDhcp6) {
+ YRTree tree;
+ ASSERT_NO_THROW_LOG(load(tree));
+ EXPECT_TRUE(verify(emptyTreeKeaDhcp6));
+
+ ConstElementPtr json = Element::fromJSON(emptyJson6);
+ EXPECT_TRUE(verify(json));
+ ASSERT_NO_THROW_LOG(load(json));
+ EXPECT_TRUE(verify(emptyJson6));
+ EXPECT_TRUE(verify(emptyTreeKeaDhcp6));
+}
+
+// Check subnet with two pools with ietf-dhcpv6-server model.
+// Validation will fail because the current model has a vendor-info
+// container with a mandatory ent-num leaf and no presence flag,
+// and of course the candidate YANG tree has nothing for this.
+TEST_F(ConfigTestIetfV6, subnetTwoPoolsIetf6) {
+ ASSERT_NO_THROW_LOG(load(subnetTwoPoolsTreeIetf6));
+ EXPECT_TRUE(verify(subnetTwoPoolsJson6));
+
+ resetSession();
+
+ ASSERT_NO_THROW_LOG(load(subnetTwoPoolsJson6));
+ EXPECT_TRUE(verify(subnetTwoPoolsTreeIetf6));
+
+ EXPECT_FALSE(validate());
+}
+
+// Check subnet with a pool and option data lists with
+// kea-dhcp4-server:config model.
+TEST_F(ConfigTestKeaV4, subnetOptionsKeaDhcp4) {
+ ASSERT_NO_THROW_LOG(load(subnetOptionsTreeKeaDhcp4));
+ EXPECT_TRUE(verify(subnetOptionsJson4));
+
+ resetSession();
+
+ ASSERT_NO_THROW_LOG(load(subnetOptionsJson4));
+ EXPECT_TRUE(verify(subnetOptionsTreeKeaDhcp4));
+
+ EXPECT_TRUE(validate());
+}
+
+// Check subnet with a pool and option data lists with
+// kea-dhcp6-server:config model.
+TEST_F(ConfigTestKeaV6, subnetOptionsKeaDhcp6) {
+ ASSERT_NO_THROW_LOG(load(subnetOptionsTreeKeaDhcp6));
+ EXPECT_TRUE(verify(subnetOptionsJson6));
+
+ resetSession();
+
+ ASSERT_NO_THROW_LOG(load(subnetOptionsJson6));
+ EXPECT_TRUE(verify(subnetOptionsTreeKeaDhcp6));
+
+ EXPECT_TRUE(validate());
+}
+
+// Check with timers.
+TEST_F(ConfigTestIetfV6, subnetTimersIetf6) {
+ ASSERT_NO_THROW_LOG(load(subnetTimersIetf6));
+ EXPECT_TRUE(verify(subnetTimersJson6));
+
+ resetSession();
+
+ ASSERT_NO_THROW_LOG(load(subnetTimersJson6));
+ EXPECT_TRUE(verify(subnetTimersIetf6));
+}
+
+// Check a ietf-dhcpv6-server configuration which validates.
+TEST_F(ConfigTestIetfV6, validateIetf6) {
+ ASSERT_NO_THROW_LOG(load(validTreeIetf6));
+ EXPECT_TRUE(verify(validTreeIetf6));
+
+ // If this validation fails, make sure you have the model *and its
+ // dependencies* are installed. Note when you install ietf-dhcpv6-server
+ // module, its dependencies are semi-installed, which is not sufficient.
+ // This can be detected in output of sysrepoctl -l.
+ // Note the ietf-interfaces module. The conformance status must be
+ // "installed". "implemented" (installed as dependency) is not enough.
+ EXPECT_TRUE(validate());
+}
+
+// Check Kea4 example files.
+TEST_F(ConfigTestKeaV4, examples4) {
+ vector<string> examples = {
+ "advanced.json",
+ "all-keys-netconf.json",
+ "all-options.json",
+ "backends.json",
+ "classify.json",
+ "classify2.json",
+ "comments.json",
+ "config-backend.json",
+ "dhcpv4-over-dhcpv6.json",
+ "global-reservations.json",
+ "ha-load-balancing-primary.json",
+ "hooks.json",
+ "hooks-radius.json",
+ "leases-expiration.json",
+ "multiple-options.json",
+ "mysql-reservations.json",
+ "pgsql-reservations.json",
+ "reservations.json",
+ "several-subnets.json",
+ "shared-network.json",
+ "single-subnet.json",
+ "vendor-specific.json",
+ "vivso.json",
+ "with-ddns.json",
+ };
+ for (string file : examples) {
+ resetSession();
+ string path = string(CFG_EXAMPLES) + "/kea4/" + file;
+ cout << "Testing file " << path << endl;
+ ConstElementPtr json;
+ ASSERT_NO_THROW_LOG(json = loadFile(path));
+ json = isc::test::moveComments(json);
+ EXPECT_TRUE(verify(json));
+ EXPECT_TRUE(validate());
+ }
+}
+
+// Check Kea6 example files.
+TEST_F(ConfigTestKeaV6, examples6) {
+ vector<string> examples = {
+ "advanced.json",
+ "all-keys-netconf.json",
+ "all-options.json",
+ "backends.json",
+ "classify.json",
+ "classify2.json",
+ "comments.json",
+ "config-backend.json",
+ "dhcpv4-over-dhcpv6.json",
+ "duid.json",
+ "global-reservations.json",
+ "ha-hot-standby.json",
+ "hooks.json",
+ "iPXE.json",
+ "leases-expiration.json",
+ "multiple-options.json",
+ "mysql-reservations.json",
+ "pgsql-reservations.json",
+ "reservations.json",
+ "several-subnets.json",
+ "shared-network.json",
+ "simple.json",
+ "softwire46.json",
+ "stateless.json",
+ "tee-times.json",
+ "with-ddns.json",
+ };
+ for (string file : examples) {
+ resetSession();
+ string path = string(CFG_EXAMPLES) + "/kea6/" + file;
+ cout << "Testing file " << path << endl;
+ ConstElementPtr json;
+ ASSERT_NO_THROW_LOG(json = loadFile(path));
+ json = isc::test::moveComments(json);
+ EXPECT_TRUE(verify(json));
+ EXPECT_TRUE(validate());
+ }
+}
+
+// Check the example in the design document.
+TEST_F(ConfigTestIetfV6, designExample) {
+ ASSERT_NO_THROW_LOG(load(designExampleTree));
+ EXPECT_TRUE(verify(designExampleJson));
+
+ resetSession();
+
+ ASSERT_NO_THROW_LOG(load(designExampleJson));
+ EXPECT_TRUE(verify(designExampleTree));
+}
+
+} // namespace