Commit 6b43936e authored by Francis Dupont's avatar Francis Dupont

[65-libyang-config-translator] Added config translator code and tests

parent 9f58cba7
......@@ -28,6 +28,7 @@ libkea_yang_la_SOURCES += translator_host.cc translator_host.h
libkea_yang_la_SOURCES += translator_subnet.cc translator_subnet.h
libkea_yang_la_SOURCES += translator_shared_network.cc
libkea_yang_la_SOURCES += translator_shared_network.h
libkea_yang_la_SOURCES += translator_config.cc translator_config.h
libkea_yang_la_SOURCES += yang_models.h
libkea_yang_la_LIBADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
......@@ -52,6 +53,7 @@ libkea_yang_include_HEADERS = \
sysrepo_error.h \
translator.h \
translator_class.h \
translator_config.h \
translator_control_socket.h \
translator_database.h \
translator_host.h \
......
......@@ -17,7 +17,7 @@ EXTRA_DIST = keatest-module.yang
TESTS =
if HAVE_GTEST
TESTS += run_unittests
run_unittests_SOURCES = yang_configs.h
run_unittests_SOURCES = json_configs.h yang_configs.h
run_unittests_SOURCES += adaptor_unittests.cc
run_unittests_SOURCES += adaptor_option_unittests.cc
run_unittests_SOURCES += adaptor_pool_unittests.cc
......@@ -38,6 +38,7 @@ run_unittests_SOURCES += translator_host_unittests.cc
run_unittests_SOURCES += translator_subnet_unittests.cc
run_unittests_SOURCES += translator_shared_network_unittests.cc
run_unittests_SOURCES += translator_utils_unittests.cc
run_unittests_SOURCES += config_unittests.cc
run_unittests_SOURCES += run_unittests.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
......
......@@ -37,7 +37,7 @@ void testFile(const std::string& fname, bool v6, ElementPtr& result) {
string decommented = decommentJSONfile(fname);
cout << "Parsing file " << fname << " (" << decommented << ")" << endl;
//cout << "Parsing file " << fname << " (" << decommented << ")" << endl;
EXPECT_NO_THROW(json = Element::fromJSONFile(decommented, true));
reference_json = moveComments(json);
......
// 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 <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/yang_configs.h>
#include <yang/tests/json_configs.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;
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:
/// @brief Constructor.
ConfigTest() {
createSession();
}
/// @brief Virtual destructor.
virtual ~ConfigTest() {
session_.reset();
connection_.reset();
model_.clear();
}
/// @brief Set model.
///
/// @param model The model name.
void setModel(const string model) {
model_ = model;
}
/// @brief Create session.
void createSession() {
connection_.reset(new Connection("configs unittests"));
session_.reset(new Session(connection_, SR_DS_CANDIDATE));
}
/// @brief Reset session.
void resetSession() {
session_.reset(new Session(connection_, SR_DS_CANDIDATE));
}
/// @brief Load Yang.
///
/// @param tree The Yang tree to load.
void load(const YRTree& tree) {
YangRepr repr(model_);
repr.set(tree, session_);
}
/// @brief Load JSON.
///
/// @param json The JSON tree to load.
void load(ConstElementPtr json) {
TranslatorConfig tc(session_, model_);
tc.setConfig(json);
}
/// @brief Load JSON text.
///
/// @param config The JSON tree to load in textual format.
void load(const string& config) {
ConstElementPtr json;
ASSERT_NO_THROW(json = Element::fromJSON(config));
load(json);
}
/// @brief Load 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 Get Yang.
YRTree getYang() {
YangRepr repr(model_);
return (repr.get(session_));
}
/// @brief Get JSON.
ConstElementPtr getJSON() {
TranslatorConfig tc(session_, model_);
return (tc.getConfig());
}
/// @brief Get JSON text.
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_;
};
// Check empty config with ietf-dhcpv6-server model.
TEST_F(ConfigTest, emptyIetf6) {
// First set the model.
setModel(IETF_DHCPV6_SERVER);
YRTree tree;
ASSERT_NO_THROW(load(tree));
EXPECT_TRUE(verify(tree));
ConstElementPtr json = Element::fromJSON(emptyJson6);
EXPECT_TRUE(verify(json));
ASSERT_NO_THROW(load(json));
EXPECT_TRUE(verify(emptyJson6));
EXPECT_TRUE(verify(tree));
}
// Check empty config with kea-dhcp4-server:config model.
TEST_F(ConfigTest, emptyKeaDhcp4) {
// First set the model.
setModel(KEA_DHCP4_SERVER);
YRTree tree;
ASSERT_NO_THROW(load(tree));
EXPECT_TRUE(verify(tree));
ConstElementPtr json = Element::fromJSON(emptyJson4);
EXPECT_TRUE(verify(json));
ASSERT_NO_THROW(load(json));
EXPECT_TRUE(verify(emptyJson4));
EXPECT_TRUE(verify(tree));
}
// Check empty config with kea-dhcp6-server:config model.
TEST_F(ConfigTest, emptyKeaDhcp6) {
// First set the model.
setModel(KEA_DHCP6_SERVER);
YRTree tree;
ASSERT_NO_THROW(load(tree));
EXPECT_TRUE(verify(tree));
ConstElementPtr json = Element::fromJSON(emptyJson6);
EXPECT_TRUE(verify(json));
ASSERT_NO_THROW(load(json));
EXPECT_TRUE(verify(emptyJson6));
EXPECT_TRUE(verify(tree));
}
// Check subnet with two pools with ietf-dhcpv6-server model.
TEST_F(ConfigTest, subnetTwoPoolsIetf6) {
// First set the model.
setModel(subnetTwoPoolsModelIetf6);
ASSERT_NO_THROW(load(subnetTwoPoolsTreeIetf6));
EXPECT_TRUE(verify(subnetTwoPoolsJson6));
resetSession();
ASSERT_NO_THROW(load(subnetTwoPoolsJson6));
EXPECT_TRUE(verify(subnetTwoPoolsTreeIetf6));
cout << "validation is expected to fail: please ignore messages" << endl;
EXPECT_FALSE(validate());
}
// Check subnet with a pool and option data lists with
// kea-dhcp4-server:config model.
TEST_F(ConfigTest, subnetOptionsKeaDhcp4) {
// First set the model.
setModel(subnetOptionsModelKeaDhcp4);
ASSERT_NO_THROW(load(subnetOptionsTreeKeaDhcp4));
EXPECT_TRUE(verify(subnetOptionsJson4));
resetSession();
ASSERT_NO_THROW(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(ConfigTest, subnetOptionsKeaDhcp6) {
// First set the model.
setModel(subnetOptionsModelKeaDhcp6);
ASSERT_NO_THROW(load(subnetOptionsTreeKeaDhcp6));
EXPECT_TRUE(verify(subnetOptionsJson6));
resetSession();
ASSERT_NO_THROW(load(subnetOptionsJson6));
EXPECT_TRUE(verify(subnetOptionsTreeKeaDhcp6));
EXPECT_TRUE(validate());
}
// Check with timers.
TEST_F(ConfigTest, subnetTimersIetf6) {
// First set the model.
setModel(subnetTimersModel);
ASSERT_NO_THROW(load(subnetTimersIetf6));
EXPECT_TRUE(verify(subnetTimersJson6));
resetSession();
ASSERT_NO_THROW(load(subnetTimersJson6));
EXPECT_TRUE(verify(subnetTimersIetf6));
}
// Check a ietf-dhcpv6-server configuration which validates.
TEST_F(ConfigTest, validateIetf6) {
// First set the model.
setModel(validModelIetf6);
ASSERT_NO_THROW(load(validTreeIetf6));
EXPECT_TRUE(verify(validTreeIetf6));
EXPECT_TRUE(validate());
}
// Check Kea4 example files.
TEST_F(ConfigTest, examples4) {
// First set the model.
setModel(KEA_DHCP4_SERVER);
vector<string> examples = {
"advanced.json",
"all-keys.json",
"backends.json",
"cassandra.json",
"classify.json",
"classify2.json",
"comments.json",
"dhcpv4-over-dhcpv6.json",
"hooks.json",
"leases-expiration.json",
"multiple-options.json",
"mysql-reservations.json",
"pgsql-reservations.json",
"reservations.json",
"several-subnets.json",
"shared-network.json",
"single-subnet.json",
"with-ddns.json"
};
for (string file : examples) {
resetSession();
string path = string(CFG_EXAMPLES) + "/kea4/" + file;
ConstElementPtr json;
ASSERT_NO_THROW(json = loadFile(path));
json = isc::test::moveComments(json);
EXPECT_TRUE(verify(json));
EXPECT_TRUE(validate());
}
}
// Check Kea6 example files.
TEST_F(ConfigTest, examples6) {
// First set the model.
setModel(KEA_DHCP6_SERVER);
vector<string> examples = {
"advanced.json",
"all-keys.json",
"backends.json",
"cassandra.json",
"classify.json",
"classify2.json",
"comments.json",
"dhcpv4-over-dhcpv6.json",
"duid.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",
"with-ddns.json"
};
for (string file : examples) {
resetSession();
string path = string(CFG_EXAMPLES) + "/kea6/" + file;
ConstElementPtr json;
ASSERT_NO_THROW(json = loadFile(path));
json = isc::test::moveComments(json);
EXPECT_TRUE(verify(json));
EXPECT_TRUE(validate());
}
}
// Check the example in the design document.
TEST_F(ConfigTest, designExample) {
// First set the model.
setModel(designExampleModel);
ASSERT_NO_THROW(load(designExampleTree));
EXPECT_TRUE(verify(designExampleJson));
resetSession();
ASSERT_NO_THROW(load(designExampleJson));
EXPECT_TRUE(verify(designExampleTree));
}
}; // end of anonymous namespace
// 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_JSON_CONFIGS_H
#define ISC_JSON_CONFIGS_H
#include <string>
namespace isc {
namespace yang {
namespace test {
/// @brief Empty DHCPv4 config.
const std::string emptyJson4 =
"{\n"
" \"Dhcp4\": {\n"
" }\n"
"}";
/// @brief A DHCPv4 config with one subnet with two pools.
const std::string subnetTwoPoolsJson4 =
"{\n"
" \"Dhcp4\": {\n"
" \"subnet4\": [\n"
" {\n"
" \"id\": 111,\n"
" \"pools\": [\n"
" {\n"
" \"pool\": \"10.0.1.0/24\"\n"
" },\n"
" {\n"
" \"pool\": \"10.0.2.0/24\"\n"
" }\n"
" ],\n"
" \"subnet\": \"10.0.0.0/8\"\n"
" }\n"
" ]\n"
" }\n"
"}";
/// @brief A DHCPv6 config with one subnet with two pools and timers.
const std::string subnetTimersJson6 =
"{\n"
" \"Dhcp6\": {\n"
" \"subnet6\": [\n"
" {\n"
" \"id\": 111,\n"
" \"renew-timer\": 1000,\n"
" \"rebind-timer\": 2000,\n"
" \"pools\": [\n"
" {\n"
" \"pool\": \"2001:db8::1:0/112\"\n"
" },\n"
" {\n"
" \"pool\": \"2001:db8::2:0/112\"\n"
" }\n"
" ],\n"
" \"subnet\": \"2001:db8::/48\"\n"
" }\n"
" ]\n"
" }\n"
"}";
/// @brief A DHCPv4 subnet with one pool and option data lists.
const std::string subnetOptionsJson4 =
"{\n"
" \"Dhcp4\": {\n"
" \"subnet4\": [\n"
" {\n"
" \"id\": 111,\n"
" \"option-data\": [\n"
" {\n"
" \"code\": 100,\n"
" \"space\": \"dns\",\n"
" \"csv-format\": false,\n"
" \"data\": \"12121212\",\n"
" \"always-send\": false\n"
" }\n"
" ],\n"
" \"pools\": [\n"
" {\n"
" \"pool\": \"10.0.1.0/24\"\n"
" }\n"
" ],\n"
" \"subnet\": \"10.0.0.0/8\"\n"
" }\n"
" ]\n"
" }\n"
"}";
/// @brief Empty DHCPv6 config.
const std::string emptyJson6 =
"{\n"
" \"Dhcp6\": {\n"
" }\n"
"}";
/// @brief A DHCPv6 config with one subnet with one pool and option data lists.
const std::string subnetOptionsJson6 =
"{\n"
" \"Dhcp6\": {\n"
" \"subnet6\": [\n"
" {\n"
" \"id\": 111,\n"
" \"pools\": [\n"
" {\n"
" \"option-data\": [\n"
" {\n"
" \"code\": 100,\n"
" \"space\": \"dns\",\n"
" \"csv-format\": false,\n"
" \"data\": \"12121212\",\n"
" \"always-send\": false\n"
" }\n"
" ],\n"
" \"pool\": \"2001:db8::1:0/112\"\n"
" }\n"
" ],\n"
" \"subnet\": \"2001:db8::/48\"\n"
" }\n"
" ]\n"
" }\n"
"}";
/// @brief A DHCPv6 config with one subnet with two pools.
const std::string subnetTwoPoolsJson6 =
"{\n"
" \"Dhcp6\": {\n"
" \"subnet6\": [\n"
" {\n"
" \"id\": 111,\n"
" \"pools\": [\n"
" {\n"
" \"pool\": \"2001:db8::1:0/112\"\n"
" },\n"
" {\n"
" \"pool\": \"2001:db8::2:0/112\"\n"
" }\n"
" ],\n"
" \"subnet\": \"2001:db8::/48\"\n"
" }\n"
" ]\n"
" }\n"
"}";
/// @brief Example from the design document.
const std::string designExampleJson =
"{\n"
" \"Dhcp6\": {\n"
" \"subnet6\": [\n"
" {\n"
" \"id\": 1,\n"
" \"subnet\": \"2001:db8:20:b00::/56\",\n"
" \"user-context\": { \"description\": \"example\" },\n"
" \"pools\": [ ],\n"
" \"pd-pools\": [\n"
" {\n"
" \"prefix\": \"2001:db8:20:b00::\",\n"
" \"prefix-len\": 57\n"
// " \"delegated-len\": 57\n"
" }\n"
" ]\n"
" }\n"
" ]\n"
" }\n"
"}";
}; // end of namespace isc::yang::test
}; // end of namespace isc::yang
}; // end of namespace isc
#endif // ISC_JSON_CONFIGS_H
......@@ -215,7 +215,7 @@ void sanityCheckConfig(const std::string& model, const YRTree& tree) {
// defined in yang_configs.h are sane.
TEST(YangReprTest, verifyConfigs) {
for (auto x : test_configs) {
cout << "Testing tree for model " << x.first << endl;
//cout << "Testing tree for model " << x.first << endl;
sanityCheckConfig(x.first, x.second);
}
}
......
This diff is collapsed.
// 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